From 550ea0c4fcf9bcb1c385af0c1ee8e7088cf4035d Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Fri, 4 Feb 2022 03:17:18 +0000 Subject: [PATCH] Use enum trick for dataclasses.MISSING The goal of this change is to fix the behavior of `dataclasses.Field`. Several attributes of a `Field` may have a value of `MISSING` (a sentinel value). As a result, attributes of Field may be checked against `MISSING` as in [f for f in fields(obj) if f.default is MISSING] `default`, `default_factory`, and `kw_only` are the attributes which may have a value of `MISSING`. This workaround of defining `_MISSING_TYPE` as an enum, and `MISSING` as its only member, allows `... | Literal[_MISSING_TYPE.MISSING]` to be used in type annotations for these attributes. This is very slightly inaccurate -- primarily in that `_MISSING_TYPE` isn't really an enum -- but this allows for use in `Literal`. After PEP 661 (Sentinel Values), there may be a more accurate way of writing this, but for now this works. This adjustment is only applied to the attributes of Field, not the arguments to functions and methods. --- stdlib/dataclasses.pyi | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/stdlib/dataclasses.pyi b/stdlib/dataclasses.pyi index 0678bd3845ae..4a8f8134bea4 100644 --- a/stdlib/dataclasses.pyi +++ b/stdlib/dataclasses.pyi @@ -1,7 +1,9 @@ +import enum import sys import types from builtins import type as Type # alias to avoid name clashes with fields named "type" from typing import Any, Callable, Generic, Iterable, Mapping, Protocol, TypeVar, overload +from typing_extensions import Literal if sys.version_info >= (3, 9): from types import GenericAlias @@ -9,9 +11,15 @@ if sys.version_info >= (3, 9): _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) -class _MISSING_TYPE: ... +# define _MISSING_TYPE as an enum within the type stubs, +# even though that is not really its type at runtime +# this allows us to use Literal[_MISSING_TYPE.MISSING] +# for background, see: +# https://github.com/python/typeshed/pull/5900#issuecomment-895513797 +class _MISSING_TYPE(enum.Enum): + MISSING = enum.auto() -MISSING: _MISSING_TYPE +MISSING = _MISSING_TYPE.MISSING if sys.version_info >= (3, 10): class KW_ONLY: ... @@ -72,15 +80,15 @@ class _DefaultFactory(Protocol[_T_co]): class Field(Generic[_T]): name: str type: Type[_T] - default: _T - default_factory: _DefaultFactory[_T] + default: _T | Literal[_MISSING_TYPE.MISSING] + default_factory: _DefaultFactory[_T] | Literal[_MISSING_TYPE.MISSING] repr: bool hash: bool | None init: bool compare: bool metadata: types.MappingProxyType[Any, Any] if sys.version_info >= (3, 10): - kw_only: bool + kw_only: bool | Literal[_MISSING_TYPE.MISSING] def __init__( self, default: _T,