Skip to content

Commit

Permalink
improve type hint detection for Iterable and NamedTuple #404
Browse files Browse the repository at this point in the history
  • Loading branch information
tfranzel committed May 31, 2021
1 parent b77d9a0 commit 9f69854
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 4 deletions.
15 changes: 12 additions & 3 deletions drf_spectacular/plumbing.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import collections
import hashlib
import inspect
import json
Expand All @@ -7,7 +8,6 @@
import urllib.parse
from abc import ABCMeta
from collections import OrderedDict, defaultdict
from collections.abc import Hashable
from decimal import Decimal
from enum import Enum
from typing import DefaultDict, Generic, List, Optional, Type, TypeVar, Union
Expand Down Expand Up @@ -84,7 +84,7 @@ def is_field(obj):


def is_basic_type(obj, allow_none=True):
if not isinstance(obj, Hashable):
if not isinstance(obj, collections.abc.Hashable):
return False
if not allow_none and (obj is None or obj is OpenApiTypes.NONE):
return False
Expand Down Expand Up @@ -927,9 +927,16 @@ def resolve_type_hint(hint):

if origin is None and is_basic_type(hint, allow_none=False):
return build_basic_type(hint)
elif origin is None and inspect.isclass(hint) and issubclass(hint, tuple):
# a convoluted way to catch NamedTuple. suggestions welcome.
if typing.get_type_hints(hint):
properties = {k: resolve_type_hint(v) for k, v in typing.get_type_hints(hint).items()}
else:
properties = {k: build_basic_type(OpenApiTypes.ANY) for k in hint._fields}
return build_object_type(properties=properties, required=properties.keys())
elif origin is list or hint is list:
return build_array_type(
resolve_type_hint(args[0]) if args else build_basic_type(OpenApiTypes.OBJECT)
resolve_type_hint(args[0]) if args else build_basic_type(OpenApiTypes.ANY)
)
elif origin is tuple:
return build_array_type(
Expand Down Expand Up @@ -967,5 +974,7 @@ def resolve_type_hint(hint):
if type(None) in args:
schema['nullable'] = True
return schema
elif origin is collections.abc.Iterable:
return build_array_type(resolve_type_hint(args[0]))
else:
raise UnableToProceedError()
21 changes: 20 additions & 1 deletion tests/test_plumbing.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import collections
import json
import re
import sys
Expand Down Expand Up @@ -83,6 +84,11 @@ def test_detype_patterns_with_module_includes(no_warnings):
)


class NamedTupleB(typing.NamedTuple):
a: int
b: str


TYPE_HINT_TEST_PARAMS = [
(
typing.Optional[int],
Expand All @@ -95,7 +101,20 @@ def test_detype_patterns_with_module_includes(no_warnings):
{'type': 'array', 'items': {'type': 'object', 'additionalProperties': {'type': 'integer'}}}
), (
list,
{'type': 'array', 'items': {'type': 'object', 'additionalProperties': {}}}
{'type': 'array', 'items': {}}
), (
typing.Iterable[collections.namedtuple("NamedTupleA", "a, b")], # noqa
{
'type': 'array',
'items': {'type': 'object', 'properties': {'a': {}, 'b': {}}, 'required': ['a', 'b']}
}
), (
NamedTupleB,
{
'type': 'object',
'properties': {'a': {'type': 'integer'}, 'b': {'type': 'string'}},
'required': ['a', 'b']
}
), (
typing.Tuple[int, int, int],
{'type': 'array', 'items': {'type': 'integer'}, 'minLength': 3, 'maxLength': 3}
Expand Down

0 comments on commit 9f69854

Please sign in to comment.