Skip to content

Commit

Permalink
Issues 1454 typeddict not required optional bug fix (#1457)
Browse files Browse the repository at this point in the history
* Fix typo, rename improts module -> imports

* Don't use Optional in TypedDict output unless property is nullable

* NotRequired[Optional] -> NotRequired in most TypedDict tests

* Improve coding styles

* Fix coverage

---------

Co-authored-by: Kyle Bebak <kyle@elementaryml.com>
Co-authored-by: Koudai Aono <koxudaxi@gmail.com>
  • Loading branch information
3 people authored Aug 3, 2023
1 parent a09ef71 commit 0deef65
Show file tree
Hide file tree
Showing 19 changed files with 174 additions and 91 deletions.
23 changes: 18 additions & 5 deletions datamodel_code_generator/model/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ def type_hint(self) -> str:
return type_hint
elif self.required:
return type_hint
return get_optional_type(type_hint, self.data_type.use_union_operator)
elif self.fall_back_to_nullable:
return get_optional_type(type_hint, self.data_type.use_union_operator)
else:
return type_hint

@property
def imports(self) -> Tuple[Import, ...]:
Expand All @@ -128,10 +131,16 @@ def imports(self) -> Tuple[Import, ...]:
)
]

if (
self.nullable or (self.nullable is None and not self.required)
) and not self.data_type.use_union_operator:
imports.append((IMPORT_OPTIONAL,))
if self.fall_back_to_nullable:
if (
self.nullable or (self.nullable is None and not self.required)
) and not self.data_type.use_union_operator:
imports.append((IMPORT_OPTIONAL,))
else:
if (
self.nullable and not self.data_type.use_union_operator
): # pragma: no cover
imports.append((IMPORT_OPTIONAL,))
if self.use_annotated:
import_annotated = (
IMPORT_ANNOTATED
Expand Down Expand Up @@ -174,6 +183,10 @@ def annotated(self) -> Optional[str]:
def has_default_factory(self) -> bool:
return 'default_factory' in self.extras

@property
def fall_back_to_nullable(self) -> bool:
return True


@lru_cache()
def get_template(template_file_path: Path) -> Template:
Expand Down
2 changes: 1 addition & 1 deletion datamodel_code_generator/model/dataclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from datamodel_code_generator.imports import Import
from datamodel_code_generator.model import DataModel, DataModelFieldBase
from datamodel_code_generator.model.base import UNDEFINED
from datamodel_code_generator.model.improts import IMPORT_DATACLASS, IMPORT_FIELD
from datamodel_code_generator.model.imports import IMPORT_DATACLASS, IMPORT_FIELD
from datamodel_code_generator.model.pydantic.base_model import Constraints
from datamodel_code_generator.reference import Reference
from datamodel_code_generator.types import chain_as_tuple
Expand Down
File renamed without changes.
6 changes: 5 additions & 1 deletion datamodel_code_generator/model/typed_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from datamodel_code_generator.imports import Import
from datamodel_code_generator.model import DataModel, DataModelFieldBase
from datamodel_code_generator.model.base import UNDEFINED
from datamodel_code_generator.model.improts import (
from datamodel_code_generator.model.imports import (
IMPORT_NOT_REQUIRED,
IMPORT_NOT_REQUIRED_BACKPORT,
IMPORT_TYPED_DICT,
Expand Down Expand Up @@ -139,6 +139,10 @@ def type_hint(self) -> str:
def _not_required(self) -> bool:
return not self.required and isinstance(self.parent, TypedDict)

@property
def fall_back_to_nullable(self) -> bool:
return not self._not_required

@property
def imports(self) -> Tuple[Import, ...]:
return (
Expand Down
12 changes: 6 additions & 6 deletions tests/data/expected/main/main_modular_typed_dict/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

from __future__ import annotations

from typing import NotRequired, Optional, TypedDict
from typing import NotRequired, TypedDict

from . import foo, models
from .nested import foo as foo_1

OptionalModel = str
Optional = str


Id = str
Expand All @@ -21,13 +21,13 @@ class Error(TypedDict):


class Result(TypedDict):
event: NotRequired[Optional[models.Event]]
event: NotRequired[models.Event]


class Source(TypedDict):
country: NotRequired[Optional[str]]
country: NotRequired[str]


class DifferentTea(TypedDict):
foo: NotRequired[Optional[foo.Tea]]
nested: NotRequired[Optional[foo_1.Tea]]
foo: NotRequired[foo.Tea]
nested: NotRequired[foo_1.Tea]
12 changes: 6 additions & 6 deletions tests/data/expected/main/main_modular_typed_dict/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from __future__ import annotations

from typing import List, Literal, NotRequired, Optional, TypedDict
from typing import List, Literal, NotRequired, TypedDict

from . import models

Expand All @@ -18,11 +18,11 @@


class Api(TypedDict):
apiKey: NotRequired[Optional[str]]
apiVersionNumber: NotRequired[Optional[str]]
apiUrl: NotRequired[Optional[str]]
apiDocumentationUrl: NotRequired[Optional[str]]
stage: NotRequired[Optional[Literal['test', 'dev', 'stg', 'prod']]]
apiKey: NotRequired[str]
apiVersionNumber: NotRequired[str]
apiUrl: NotRequired[str]
apiDocumentationUrl: NotRequired[str]
stage: NotRequired[Literal['test', 'dev', 'stg', 'prod']]


Apis = List[Api]
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

from __future__ import annotations

from typing import NotRequired, Optional, TypedDict
from typing import NotRequired, TypedDict

from .. import Id


class Tea(TypedDict):
flavour: NotRequired[Optional[str]]
id: NotRequired[Optional[Id]]
flavour: NotRequired[str]
id: NotRequired[Id]


class Cocoa(TypedDict):
quality: NotRequired[Optional[int]]
quality: NotRequired[int]
10 changes: 5 additions & 5 deletions tests/data/expected/main/main_modular_typed_dict/foo/bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@

from __future__ import annotations

from typing import Any, Dict, List, NotRequired, Optional, TypedDict
from typing import Any, Dict, List, NotRequired, TypedDict


class Thing(TypedDict):
attributes: NotRequired[Optional[Dict[str, Any]]]
attributes: NotRequired[Dict[str, Any]]


class Thang(TypedDict):
attributes: NotRequired[Optional[List[Dict[str, Any]]]]
attributes: NotRequired[List[Dict[str, Any]]]


class Others(TypedDict):
name: NotRequired[Optional[str]]
name: NotRequired[str]


class Clone(Thing):
others: NotRequired[Optional[Others]]
others: NotRequired[Others]
10 changes: 5 additions & 5 deletions tests/data/expected/main/main_modular_typed_dict/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@

from __future__ import annotations

from typing import Any, Dict, List, Literal, NotRequired, Optional, TypedDict, Union
from typing import Any, Dict, List, Literal, NotRequired, TypedDict, Union

Species = Literal['dog', 'cat', 'snake']


class Pet(TypedDict):
id: int
name: str
tag: NotRequired[Optional[str]]
species: NotRequired[Optional[Species]]
tag: NotRequired[str]
species: NotRequired[Species]


class User(TypedDict):
id: int
name: str
tag: NotRequired[Optional[str]]
tag: NotRequired[str]


class Event(TypedDict):
name: NotRequired[Optional[Union[str, float, int, bool, Dict[str, Any], List[str]]]]
name: NotRequired[Union[str, float, int, bool, Dict[str, Any], List[str]]]
20 changes: 10 additions & 10 deletions tests/data/expected/main/main_modular_typed_dict/nested/foo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@

from __future__ import annotations

from typing import List, NotRequired, Optional, TypedDict
from typing import List, NotRequired, TypedDict

from .. import Id, OptionalModel
from .. import Id, Optional


class Tea(TypedDict):
flavour: NotRequired[Optional[str]]
id: NotRequired[Optional[Id]]
self: NotRequired[Optional[Tea]]
optional: NotRequired[Optional[List[OptionalModel]]]
flavour: NotRequired[str]
id: NotRequired[Id]
self: NotRequired[Tea]
optional: NotRequired[List[Optional]]


class TeaClone(TypedDict):
flavour: NotRequired[Optional[str]]
id: NotRequired[Optional[Id]]
self: NotRequired[Optional[Tea]]
optional: NotRequired[Optional[List[OptionalModel]]]
flavour: NotRequired[str]
id: NotRequired[Id]
self: NotRequired[Tea]
optional: NotRequired[List[Optional]]


ListModel = List[Tea]
10 changes: 5 additions & 5 deletions tests/data/expected/main/main_modular_typed_dict/woo/boo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

from __future__ import annotations

from typing import NotRequired, Optional, TypedDict
from typing import NotRequired, TypedDict

from .. import Source, bar, foo


class Chocolate(TypedDict):
flavour: NotRequired[Optional[str]]
source: NotRequired[Optional[Source]]
cocoa: NotRequired[Optional[foo.Cocoa]]
field: NotRequired[Optional[bar.Field]]
flavour: NotRequired[str]
source: NotRequired[Source]
cocoa: NotRequired[foo.Cocoa]
field: NotRequired[bar.Field]
18 changes: 9 additions & 9 deletions tests/data/expected/main/main_typed_dict/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

from __future__ import annotations

from typing import List, Optional
from typing import List

from typing_extensions import NotRequired, TypedDict


class Pet(TypedDict):
id: int
name: str
tag: NotRequired[Optional[str]]
tag: NotRequired[str]


Pets = List[Pet]
Expand All @@ -21,7 +21,7 @@ class Pet(TypedDict):
class User(TypedDict):
id: int
name: str
tag: NotRequired[Optional[str]]
tag: NotRequired[str]


Users = List[User]
Expand All @@ -39,18 +39,18 @@ class Error(TypedDict):


class Api(TypedDict):
apiKey: NotRequired[Optional[str]]
apiVersionNumber: NotRequired[Optional[str]]
apiUrl: NotRequired[Optional[str]]
apiDocumentationUrl: NotRequired[Optional[str]]
apiKey: NotRequired[str]
apiVersionNumber: NotRequired[str]
apiUrl: NotRequired[str]
apiDocumentationUrl: NotRequired[str]


Apis = List[Api]


class Event(TypedDict):
name: NotRequired[Optional[str]]
name: NotRequired[str]


class Result(TypedDict):
event: NotRequired[Optional[Event]]
event: NotRequired[Event]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# generated by datamodel-codegen:
# filename: not_required_nullable.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from typing import NotRequired, Optional, TypedDict


class Person(TypedDict):
name: str
null_name: NotRequired[Optional[str]]
age: NotRequired[int]
null_age: Optional[int]
16 changes: 8 additions & 8 deletions tests/data/expected/main/main_typed_dict_nullable/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

class Cursors(TypedDict):
prev: str
next: NotRequired[Optional[str]]
next: NotRequired[str]
index: float
tag: NotRequired[Optional[str]]
tag: NotRequired[str]


class TopLevel(TypedDict):
Expand All @@ -27,10 +27,10 @@ class User(TypedDict):


class Api(TypedDict):
apiKey: NotRequired[Optional[str]]
apiVersionNumber: NotRequired[Optional[str]]
apiUrl: NotRequired[Optional[str]]
apiDocumentationUrl: NotRequired[Optional[str]]
apiKey: NotRequired[str]
apiVersionNumber: NotRequired[str]
apiUrl: NotRequired[str]
apiDocumentationUrl: NotRequired[str]


Apis = Optional[List[Api]]
Expand All @@ -39,8 +39,8 @@ class Api(TypedDict):
class EmailItem(TypedDict):
author: str
address: str
description: NotRequired[Optional[str]]
tag: NotRequired[Optional[str]]
description: NotRequired[str]
tag: NotRequired[str]


Email = List[EmailItem]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Cursors(TypedDict):
prev: Optional[str]
next: NotRequired[str]
index: float
tag: NotRequired[Optional[str]]
tag: NotRequired[str]


class TopLevel(TypedDict):
Expand All @@ -27,10 +27,10 @@ class User(TypedDict):


class Api(TypedDict):
apiKey: NotRequired[Optional[str]]
apiVersionNumber: NotRequired[Optional[str]]
apiUrl: NotRequired[Optional[str]]
apiDocumentationUrl: NotRequired[Optional[str]]
apiKey: NotRequired[str]
apiVersionNumber: NotRequired[str]
apiUrl: NotRequired[str]
apiDocumentationUrl: NotRequired[str]


Apis = Optional[List[Api]]
Expand All @@ -40,7 +40,7 @@ class EmailItem(TypedDict):
author: str
address: str
description: NotRequired[str]
tag: NotRequired[Optional[str]]
tag: NotRequired[str]


Email = List[EmailItem]
Expand Down
Loading

0 comments on commit 0deef65

Please sign in to comment.