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

Add --disable-appending-item-suffix #368

Merged
merged 2 commits into from
Mar 6, 2021
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ $ pip install datamodel-code-generator[http]
The `datamodel-codegen` command:
```
usage: datamodel-codegen [-h] [--url URL] [--input INPUT] [--input-file-type {auto,openapi,jsonschema,json,yaml,dict,csv}] [--output OUTPUT]
[--base-class BASE_CLASS] [--field-constraints] [--snake-case-field] [--strip-default-none]
[--base-class BASE_CLASS] [--field-constraints] [--snake-case-field] [--strip-default-none] [--disable-appending-item-suffix]
[--allow-population-by-field-name] [--enable-faux-immutability] [--use-default] [--force-optional] [--strict-nullable]
[--disable-timestamp] [--use-standard-collections] [--use-generic-container-types] [--use-schema-description]
[--reuse-model] [--enum-field-as-literal {all,one}] [--set-default-enum-member] [--class-name CLASS_NAME]
Expand All @@ -86,6 +86,8 @@ optional arguments:
--field-constraints Use field constraints and not con* annotations
--snake-case-field Change camel-case field name to snake-case
--strip-default-none Strip default None on fields
--disable-appending-item-suffix
Disable appending `Item` suffix to model name in an array
--allow-population-by-field-name
Allow population by field name
--enable-faux-immutability
Expand Down
2 changes: 2 additions & 0 deletions datamodel_code_generator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ def generate(
strict_nullable: bool = False,
use_generic_container_types: bool = False,
enable_faux_immutability: bool = False,
disable_appending_item_suffix: bool = False,
) -> None:

remote_text_cache: DefaultPutDict[str, str] = DefaultPutDict()
Expand Down Expand Up @@ -308,6 +309,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> Dict[str, Any]:
use_generic_container_types=use_generic_container_types,
enable_faux_immutability=enable_faux_immutability,
remote_text_cache=remote_text_cache,
disable_appending_item_suffix=disable_appending_item_suffix,
)

with chdir(output):
Expand Down
9 changes: 8 additions & 1 deletion datamodel_code_generator/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,12 @@ def sig_int_handler(_: int, __: Any) -> None: # pragma: no cover
action='store_true',
default=None,
)

arg_parser.add_argument(
'--disable-appending-item-suffix',
help='Disable appending `Item` suffix to model name in an array',
action='store_true',
default=None,
)
arg_parser.add_argument(
'--allow-population-by-field-name',
help='Allow population by field name',
Expand Down Expand Up @@ -288,6 +293,7 @@ def validate_use_generic_container_types(
use_generic_container_types: bool = False
enable_faux_immutability: bool = False
url: Optional[ParseResult] = None
disable_appending_item_suffix: bool = False

def merge_args(self, args: Namespace) -> None:
for field_name in self.__fields__:
Expand Down Expand Up @@ -405,6 +411,7 @@ def main(args: Optional[Sequence[str]] = None) -> Exit:
strict_nullable=config.strict_nullable,
use_generic_container_types=config.use_generic_container_types,
enable_faux_immutability=config.enable_faux_immutability,
disable_appending_item_suffix=config.disable_appending_item_suffix,
)
return Exit.OK
except InvalidClassNameError as e:
Expand Down
2 changes: 2 additions & 0 deletions datamodel_code_generator/parser/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ def __init__(
use_generic_container_types: bool = False,
enable_faux_immutability: bool = False,
remote_text_cache: Optional[DefaultPutDict[str, str]] = None,
disable_appending_item_suffix: bool = False,
):
self.data_type_manager: DataTypeManager = data_type_manager_type(
target_python_version, use_standard_collections, use_generic_container_types
Expand Down Expand Up @@ -310,6 +311,7 @@ def __init__(
self.model_resolver = ModelResolver(
aliases=aliases,
base_url=source.geturl() if isinstance(source, ParseResult) else None,
singular_name_suffix='' if disable_appending_item_suffix else None,
)
self.field_preprocessors: List[
Callable[[DataModelFieldBase, DataModel], None]
Expand Down
22 changes: 5 additions & 17 deletions datamodel_code_generator/parser/jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ def __init__(
use_generic_container_types: bool = False,
enable_faux_immutability: bool = False,
remote_text_cache: Optional[DefaultPutDict[str, str]] = None,
disable_appending_item_suffix: bool = False,
):
super().__init__(
source=source,
Expand Down Expand Up @@ -290,6 +291,7 @@ def __init__(
use_generic_container_types=use_generic_container_types,
enable_faux_immutability=enable_faux_immutability,
remote_text_cache=remote_text_cache,
disable_appending_item_suffix=disable_appending_item_suffix,
)

self.remote_object_cache: DefaultPutDict[str, Dict[str, Any]] = DefaultPutDict()
Expand Down Expand Up @@ -428,9 +430,7 @@ def parse_all_of(
# ignore an undetected object
if ignore_duplicate_model and not fields and len(base_classes) == 1:
return self.data_type(reference=base_classes[0])
reference = self.model_resolver.add(
path, name, class_name=True, unique=True, loaded=True
)
reference = self.model_resolver.add(path, name, class_name=True, loaded=True)
self.set_additional_properties(reference.name, obj)
data_model_type = self.data_model_type(
reference=reference,
Expand Down Expand Up @@ -611,12 +611,7 @@ def parse_object(
f'This argument will be removed in a future version'
)
reference = self.model_resolver.add(
path,
name,
class_name=True,
singular_name=singular_name,
unique=True,
loaded=True,
path, name, class_name=True, singular_name=singular_name, loaded=True,
)
class_name = reference.name
self.set_title(class_name, obj)
Expand All @@ -641,11 +636,7 @@ def parse_field(index: int, item: JsonSchemaObject) -> DataType:
if item.has_constraint and (obj.has_constraint or self.field_constraints):
return self.parse_root_type(
self.model_resolver.add(
field_path,
name,
class_name=True,
singular_name=True,
unique=True,
field_path, name, class_name=True, singular_name=True,
).name,
item,
field_path,
Expand Down Expand Up @@ -833,7 +824,6 @@ def parse_enum(
class_name=True,
singular_name=singular_name,
singular_name_suffix='Enum',
unique=True,
loaded=True,
)
enum = Enum(
Expand All @@ -851,7 +841,6 @@ def parse_enum(
class_name=True,
singular_name=singular_name,
singular_name_suffix='Enum',
unique=True,
loaded=True,
)
enum_reference = self.model_resolver.add(
Expand All @@ -860,7 +849,6 @@ def parse_enum(
class_name=True,
singular_name=singular_name,
singular_name_suffix='Enum',
unique=True,
loaded=True,
)
enum = Enum(
Expand Down
26 changes: 20 additions & 6 deletions datamodel_code_generator/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ def short_name(self) -> str:
return self.name.rsplit('.', 1)[-1]


SINGULAR_NAME_SUFFIX: str = 'Item'

ID_PATTERN: Pattern[str] = re.compile(r'^#[^/].*')

T = TypeVar('T')
Expand All @@ -130,6 +132,7 @@ def __init__(
exclude_names: Set[str] = None,
duplicate_name_suffix: Optional[str] = None,
base_url: Optional[str] = None,
singular_name_suffix: Optional[str] = None,
) -> None:
self.references: Dict[str, Reference] = {}
self.aliases: Mapping[str, str] = {} if aliases is None else {**aliases}
Expand All @@ -140,6 +143,9 @@ def __init__(
self.exclude_names: Set[str] = exclude_names or set()
self.duplicate_name_suffix: Optional[str] = duplicate_name_suffix
self._base_url: Optional[str] = base_url
self.singular_name_suffix: str = singular_name_suffix if isinstance(
singular_name_suffix, str
) else SINGULAR_NAME_SUFFIX

@property
def base_url(self) -> Optional[str]:
Expand Down Expand Up @@ -276,8 +282,8 @@ def add(
*,
class_name: bool = False,
singular_name: bool = False,
unique: bool = False,
singular_name_suffix: str = 'Item',
unique: bool = True,
singular_name_suffix: Optional[str] = None,
loaded: bool = False,
) -> Reference:
joined_path = self.join_path(path)
Expand All @@ -293,9 +299,13 @@ def add(
return reference
name = original_name
if singular_name:
name = get_singular_name(name, singular_name_suffix)
name = get_singular_name(
name, singular_name_suffix or self.singular_name_suffix
)
if class_name:
name = self.get_class_name(name, unique)
name = self.get_class_name(
name, unique, reference.name if reference else None
)
elif unique:
name = self._get_uniq_name(name)
if reference:
Expand All @@ -312,7 +322,9 @@ def add(
def get(self, path: Union[Sequence[str], str]) -> Optional[Reference]:
return self.references.get(self.resolve_ref(path))

def get_class_name(self, field_name: str, unique: bool = True) -> str:
def get_class_name(
self, field_name: str, unique: bool = True, reserved_name: Optional[str] = None
) -> str:
if '.' in field_name:
split_name = [self.get_valid_name(n) for n in field_name.split('.')]
prefix, field_name = '.'.join(split_name[:-1]), split_name[-1]
Expand All @@ -323,6 +335,8 @@ def get_class_name(self, field_name: str, unique: bool = True) -> str:
field_name = self.get_valid_name(field_name)
upper_camel_name = snake_to_upper_camel(field_name)
if unique:
if reserved_name == upper_camel_name:
return upper_camel_name
class_name = self._get_uniq_name(upper_camel_name, camel=True)
else:
class_name = upper_camel_name
Expand Down Expand Up @@ -377,7 +391,7 @@ def get_valid_field_name_and_alias(


@lru_cache()
def get_singular_name(name: str, suffix: str = 'Item') -> str:
def get_singular_name(name: str, suffix: str = SINGULAR_NAME_SUFFIX) -> str:
singular_name = inflect_engine.singular_noun(name)
if singular_name is False:
singular_name = f'{name}{suffix}'
Expand Down
4 changes: 3 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ $ pip install datamodel-code-generator[http]
The `datamodel-codegen` command:
```
usage: datamodel-codegen [-h] [--input INPUT] [--url URL] [--input-file-type {auto,openapi,jsonschema,json,yaml,dict,csv}] [--output OUTPUT]
[--base-class BASE_CLASS] [--field-constraints] [--snake-case-field] [--strip-default-none]
[--base-class BASE_CLASS] [--field-constraints] [--snake-case-field] [--strip-default-none] [--disable-appending-item-suffix]
[--allow-population-by-field-name] [--enable-faux-immutability] [--use-default] [--force-optional] [--strict-nullable]
[--disable-timestamp] [--use-standard-collections] [--use-generic-container-types] [--use-schema-description]
[--reuse-model] [--enum-field-as-literal {all,one}] [--set-default-enum-member] [--class-name CLASS_NAME]
Expand All @@ -53,6 +53,8 @@ optional arguments:
--field-constraints Use field constraints and not con* annotations
--snake-case-field Change camel-case field name to snake-case
--strip-default-none Strip default None on fields
--disable-appending-item-suffix
Disable appending `Item` suffix to model name in an array
--allow-population-by-field-name
Allow population by field name
--enable-faux-immutability
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# generated by datamodel-codegen:
# filename: api_constrained.yaml
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from typing import List, Optional

from pydantic import AnyUrl, BaseModel, Field


class Pet(BaseModel):
id: int = Field(..., ge=0.0)
name: str = Field(..., max_length=256)
tag: Optional[str] = Field(None, max_length=64)


class Pets(BaseModel):
__root__: List[Pet] = Field(..., max_items=10, min_items=1)


class UID(BaseModel):
__root__: int = Field(..., ge=0.0)


class Phone(BaseModel):
__root__: str = Field(..., min_length=3)


class Fax(BaseModel):
__root__: str = Field(..., min_length=3)


class User(BaseModel):
id: int = Field(..., ge=0.0)
name: str = Field(..., max_length=256)
tag: Optional[str] = Field(None, max_length=64)
uid: UID
phones: Optional[List[Phone]] = Field(None, max_items=10)
fax: Optional[List[Fax]] = None


class Users(BaseModel):
__root__: List[User]


class Id(BaseModel):
__root__: str


class Rules(BaseModel):
__root__: List[str]


class Error(BaseModel):
code: int
message: str


class Api(BaseModel):
apiKey: Optional[str] = Field(
None, description='To be used as a dataset parameter value'
)
apiVersionNumber: Optional[str] = Field(
None, description='To be used as a version parameter value'
)
apiUrl: Optional[AnyUrl] = Field(
None, description="The URL describing the dataset's fields"
)
apiDocumentationUrl: Optional[AnyUrl] = Field(
None, description='A URL to the API console for each API'
)


class Apis(BaseModel):
__root__: List[Api]


class Event(BaseModel):
name: Optional[str] = None


class Result(BaseModel):
event: Optional[Event] = None
2 changes: 0 additions & 2 deletions tests/parser/test_jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
import pytest
import yaml

from datamodel_code_generator import DataModelField, DataTypeManager
from datamodel_code_generator.imports import IMPORT_OPTIONAL, Import
from datamodel_code_generator.model import DataModelFieldBase
from datamodel_code_generator.model.pydantic import BaseModel, CustomRootType
from datamodel_code_generator.parser.base import dump_templates
from datamodel_code_generator.parser.jsonschema import (
JsonSchemaObject,
Expand Down
26 changes: 26 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2261,3 +2261,29 @@ def test_main_self_reference():
)
with pytest.raises(SystemExit):
main()


@freeze_time('2019-07-26')
def test_main_disable_appending_item_suffix():
with TemporaryDirectory() as output_dir:
output_file: Path = Path(output_dir) / 'output.py'
return_code: Exit = main(
[
'--input',
str(OPEN_API_DATA_PATH / 'api_constrained.yaml'),
'--output',
str(output_file),
'--field-constraints',
'--disable-appending-item-suffix',
]
)
assert return_code == Exit.OK
assert (
output_file.read_text()
== (
EXPECTED_MAIN_PATH / 'main_disable_appending_item_suffix' / 'output.py'
).read_text()
)

with pytest.raises(SystemExit):
main()