Skip to content

Commit 392458d

Browse files
committed
fixups
1 parent 07a797c commit 392458d

File tree

2 files changed

+28
-9
lines changed

2 files changed

+28
-9
lines changed

fgpyo/util/types.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import collections
22
import inspect
33
import sys
4+
import types
45
import typing
56
from enum import Enum
67
from functools import partial
@@ -85,7 +86,7 @@ def is_constructible_from_str(type_: type) -> bool:
8586

8687

8788
# NB: since `_GenericAlias` is a private attribute of the `typing` module, mypy doesn't find it
88-
TypeAnnotation: TypeAlias = Union[type, typing._GenericAlias, UnionType] # type: ignore[name-defined]
89+
TypeAnnotation: TypeAlias = Union[type, typing._GenericAlias, UnionType, types.GenericAlias] # type: ignore[name-defined]
8990
"""
9091
A function parameter's type annotation may be any of the following:
9192
1) `type`, when declaring any of the built-in Python types
@@ -101,20 +102,27 @@ def is_constructible_from_str(type_: type) -> bool:
101102
# TODO When dropping support for Python 3.9, deprecate this in favor of performing instance checks
102103
# directly on the `TypeAnnotation` union type.
103104
# NB: since `_GenericAlias` is a private attribute of the `typing` module, mypy doesn't find it
104-
TYPE_ANNOTATION_TYPES = (type, typing._GenericAlias, UnionType) # type: ignore[attr-defined]
105+
TYPE_ANNOTATION_TYPES = (type, typing._GenericAlias, UnionType, types.GenericAlias) # type: ignore[attr-defined]
105106

106107

107108
def _is_optional(dtype: TypeAnnotation) -> bool:
108-
"""Check if a type is `Optional`.
109+
"""
110+
Check if a type is `Optional`.
111+
109112
An optional type may be declared using three syntaxes: `Optional[T]`, `Union[T, None]`, or `T |
110-
None`. All of these syntaxes is supported by this function.
113+
None`. All of these syntaxes are supported by this function.
114+
111115
Args:
112116
dtype: A type.
117+
113118
Returns:
114119
True if the type is a union type with exactly two elements, one of which is `None`.
115120
False otherwise.
121+
116122
Raises:
117-
TypeError: If the input is not a valid `TypeAnnotation` type (see above).
123+
TypeError: If the input is not a valid `TypeAnnotation` type.
124+
Type annotations may be any of `type`, `types.UnionType`, `types.GenericAlias`,
125+
or `typing._GenericAlias`.
118126
"""
119127
if not isinstance(dtype, TYPE_ANNOTATION_TYPES):
120128
raise TypeError(f"Expected type annotation, got {type(dtype)}: {dtype}")

tests/fgpyo/util/test_types.py

+15-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
from typing import List
44
from typing import Optional
55
from typing import Sequence
6+
from typing import Type
67
from typing import Union
78

9+
import pytest
10+
811
from fgpyo.util import types
912
from fgpyo.util.inspect import NoneType
1013

@@ -16,10 +19,18 @@ def test_is_listlike() -> None:
1619
assert not types.is_list_like(str)
1720

1821

19-
def test_is_optional() -> None:
20-
assert types._is_optional(Union[str, NoneType])
21-
assert types._is_optional(Optional[str])
22-
assert not types._is_optional(str)
22+
@pytest.mark.parametrize(
23+
"tpe, expected",
24+
[
25+
(Union[str, NoneType], True),
26+
(Optional[str], True),
27+
(Union[str, int], False),
28+
(Union[str, int, None], False),
29+
(str, False),
30+
],
31+
)
32+
def test_is_optional(tpe: Type, expected: bool) -> None:
33+
assert types._is_optional(tpe) == expected
2334

2435

2536
if sys.version_info >= (3, 10):

0 commit comments

Comments
 (0)