Skip to content

Commit

Permalink
Support customTypePath (#436)
Browse files Browse the repository at this point in the history
* Support customTypePath

* Fix unittest
  • Loading branch information
koxudaxi authored May 28, 2021
1 parent e008896 commit 80bd7ab
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 0 deletions.
1 change: 1 addition & 0 deletions datamodel_code_generator/model/pydantic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Config(_BaseModel):
title: Optional[str] = None
allow_population_by_field_name: Optional[bool] = None
allow_mutation: Optional[bool] = None
arbitrary_types_allowed: Optional[bool] = None


# def get_validator_template() -> Template:
Expand Down
4 changes: 4 additions & 0 deletions datamodel_code_generator/model/pydantic/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ def __init__(
config_parameters[config_attribute] = self.extra_template_data[
config_attribute
]
for data_type in self.all_data_types:
if data_type.is_custom_type:
config_parameters['arbitrary_types_allowed'] = True
break

if config_parameters:
from datamodel_code_generator.model.pydantic import Config
Expand Down
9 changes: 9 additions & 0 deletions datamodel_code_generator/parser/jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ def validate_ref(cls, value: Any) -> Any:
examples: Any
default: Any
id: Optional[str] = Field(default=None, alias='$id')
custom_type_path: Optional[str] = Field(default=None, alias='customTypePath')
_raw: Dict[str, Any]

class Config:
Expand Down Expand Up @@ -588,6 +589,10 @@ def parse_item(
)
elif item.ref:
return self.get_ref_data_type(item.ref)
elif item.custom_type_path:
return self.data_type_manager.get_data_type_from_full_path(
item.custom_type_path, is_custom_type=True
)
elif item.is_array:
return self.parse_array_fields(
name, item, get_special_path('array', path)
Expand Down Expand Up @@ -754,6 +759,10 @@ def parse_root_type(
) -> DataType:
if obj.ref:
data_type: DataType = self.get_ref_data_type(obj.ref)
elif obj.custom_type_path:
data_type = self.data_type_manager.get_data_type_from_full_path(
obj.custom_type_path, is_custom_type=True
)
elif obj.is_object or obj.anyOf or obj.oneOf:
data_types: List[DataType] = []
object_path = [*path, name]
Expand Down
10 changes: 10 additions & 0 deletions datamodel_code_generator/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class DataType(_BaseModel):
is_optional: bool = False
is_dict: bool = False
is_list: bool = False
is_custom_type: bool = False
literals: List[str] = []
use_standard_collections: bool = False
use_generic_container: bool = False
Expand All @@ -89,6 +90,7 @@ def from_import(
is_optional: bool = False,
is_dict: bool = False,
is_list: bool = False,
is_custom_type: bool = False,
strict: bool = False,
kwargs: Optional[Dict[str, Any]] = None,
) -> 'DataTypeT':
Expand All @@ -99,6 +101,7 @@ def from_import(
is_dict=is_dict,
is_list=is_list,
is_func=True if kwargs else False,
is_custom_type=is_custom_type,
strict=strict,
kwargs=kwargs,
)
Expand Down Expand Up @@ -336,3 +339,10 @@ def __init__(
@abstractmethod
def get_data_type(self, types: Types, **kwargs: Any) -> DataType:
raise NotImplementedError

def get_data_type_from_full_path(
self, full_path: str, is_custom_type: bool
) -> DataType:
return self.data_type.from_import(
Import.from_full_path(full_path), is_custom_type=is_custom_type
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# generated by datamodel-codegen:
# filename: custom_type_path.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from typing import Optional

from pydantic import BaseModel, Field

from custom import MultipleLineString, SpecialString, TitleString
from custom.collection.array import Friends
from custom.special import UpperString
from custom.special.numbers import Age


class Person(BaseModel):
class Config:
arbitrary_types_allowed = True

firstName: Optional[TitleString] = Field(
None, description="The person's first name."
)
lastName: Optional[UpperString] = Field(None, description="The person's last name.")
age: Optional[Age] = Field(
None, description='Age in years which must be equal to or greater than zero.'
)
friends: Optional[Friends] = None
comment: Optional[MultipleLineString] = None


class RootedCustomType(BaseModel):
class Config:
arbitrary_types_allowed = True

__root__: SpecialString
37 changes: 37 additions & 0 deletions tests/data/jsonschema/custom_type_path.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "The person's first name.",
"customTypePath": "custom.TitleString"
},
"lastName": {
"type": "string",
"description": "The person's last name.",
"customTypePath": "custom.special.UpperString"
},
"age": {
"description": "Age in years which must be equal to or greater than zero.",
"type": "integer",
"minimum": 0,
"customTypePath": "custom.special.numbers.Age"
},
"friends": {
"type": "array",
"customTypePath": "custom.collection.array.Friends"
},
"comment": {
"type": "null",
"customTypePath": "custom.MultipleLineString"
}
},
"definitions": {
"RootedCustomType": {
"type": "string",
"customTypePath": "custom.SpecialString"
}
}
}
30 changes: 30 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from tempfile import TemporaryDirectory
from unittest.mock import call

import isort
import pytest
from _pytest.capture import CaptureFixture
from _pytest.tmpdir import TempdirFactory
Expand Down Expand Up @@ -2884,3 +2885,32 @@ def test_main_jsonschema_field_extras():
)
with pytest.raises(SystemExit):
main()


@pytest.mark.skipif(
not isort.__version__.startswith('4.'),
reason="isort 5.x don't sort pydantic modules",
)
@freeze_time('2019-07-26')
def test_main_jsonschema_custom_type_path():
with TemporaryDirectory() as output_dir:
output_file: Path = Path(output_dir) / 'output.py'
return_code: Exit = main(
[
'--input',
str(JSON_SCHEMA_DATA_PATH / 'custom_type_path.json'),
'--output',
str(output_file),
'--input-file-type',
'jsonschema',
]
)
assert return_code == Exit.OK
assert (
output_file.read_text()
== (
EXPECTED_MAIN_PATH / 'main_jsonschema_custom_type_path' / 'output.py'
).read_text()
)
with pytest.raises(SystemExit):
main()

0 comments on commit 80bd7ab

Please sign in to comment.