Skip to content

Commit d18763c

Browse files
Introduce NothingType (#1358)
* Introduce NothingType * Fallback to typing_extensions * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Avoid TypeAlias at runtime * Add changelog * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Docs * Add import, test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix test --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 1e07f46 commit d18763c

File tree

7 files changed

+28
-4
lines changed

7 files changed

+28
-4
lines changed

changelog.d/1358.change.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Introduce `attrs.NothingType`, for annotating types consistent with `attrs.NOTHING`.

src/attr/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
from functools import partial
8-
from typing import Callable, Protocol
8+
from typing import Callable, Literal, Protocol
99

1010
from . import converters, exceptions, filters, setters, validators
1111
from ._cmp import cmp_using
@@ -16,6 +16,7 @@
1616
Attribute,
1717
Converter,
1818
Factory,
19+
_Nothing,
1920
attrib,
2021
attrs,
2122
fields,
@@ -36,12 +37,15 @@ class AttrsInstance(Protocol):
3637
pass
3738

3839

40+
NothingType = Literal[_Nothing.NOTHING]
41+
3942
__all__ = [
4043
"NOTHING",
4144
"Attribute",
4245
"AttrsInstance",
4346
"Converter",
4447
"Factory",
48+
"NothingType",
4549
"asdict",
4650
"assoc",
4751
"astuple",

src/attr/__init__.pyi

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ from typing import (
55
Any,
66
Callable,
77
Generic,
8+
Literal,
89
Mapping,
910
Protocol,
1011
Sequence,
@@ -37,9 +38,9 @@ from attrs import (
3738
)
3839

3940
if sys.version_info >= (3, 10):
40-
from typing import TypeGuard
41+
from typing import TypeGuard, TypeAlias
4142
else:
42-
from typing_extensions import TypeGuard
43+
from typing_extensions import TypeGuard, TypeAlias
4344

4445
if sys.version_info >= (3, 11):
4546
from typing import dataclass_transform
@@ -72,11 +73,11 @@ class _Nothing(enum.Enum):
7273
NOTHING = enum.auto()
7374

7475
NOTHING = _Nothing.NOTHING
76+
NothingType: TypeAlias = Literal[_Nothing.NOTHING]
7577

7678
# NOTE: Factory lies about its return type to make this possible:
7779
# `x: List[int] # = Factory(list)`
7880
# Work around mypy issue #4554 in the common case by using an overload.
79-
from typing import Literal
8081

8182
@overload
8283
def Factory(factory: Callable[[], _T]) -> _T: ...

src/attr/_make.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ def __bool__(self):
7979
NOTHING = _Nothing.NOTHING
8080
"""
8181
Sentinel to indicate the lack of a value when `None` is ambiguous.
82+
83+
When using in 3rd party code, use `attrs.NothingType` for type annotations.
8284
"""
8385

8486

src/attrs/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
AttrsInstance,
77
Converter,
88
Factory,
9+
NothingType,
910
_make_getattr,
1011
assoc,
1112
cmp_using,
@@ -32,6 +33,7 @@
3233
"AttrsInstance",
3334
"Converter",
3435
"Factory",
36+
"NothingType",
3537
"__author__",
3638
"__copyright__",
3739
"__description__",

src/attrs/__init__.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ from attr import setters as setters
4040
from attr import validate as validate
4141
from attr import validators as validators
4242
from attr import attrib, asdict as asdict, astuple as astuple
43+
from attr import NothingType as NothingType
4344

4445
if sys.version_info >= (3, 11):
4546
from typing import dataclass_transform

tests/test_mypy.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,3 +1472,16 @@
14721472
reveal_type(A) # N: Revealed type is "def () -> main.A"
14731473
if has(A):
14741474
reveal_type(A) # N: Revealed type is "type[attr.AttrsInstance]"
1475+
1476+
- case: testNothingType
1477+
regex: true
1478+
main: |
1479+
from typing import Optional
1480+
from attrs import NOTHING, NothingType
1481+
1482+
def takes_nothing(arg: Optional[NothingType]) -> None:
1483+
return None
1484+
1485+
takes_nothing(NOTHING)
1486+
takes_nothing(None)
1487+
takes_nothing(1) # E: Argument 1 to "takes_nothing" has incompatible type "Literal\[1\]"; expected "(Optional\[Literal\[_Nothing.NOTHING\]\]|Literal\[_Nothing.NOTHING\] \| None)" \[arg-type\]

0 commit comments

Comments
 (0)