We have lots of examples of using Empty to sentinel an unset value, e.g.:
def signature_model(self) -> type[SignatureModel]:
if self._signature_model is Empty:
self._signature_model = SignatureModel.create(
dependency_name_set=self.dependency_name_set,
fn=cast("AnyCallable", self.fn),
parsed_signature=self.parsed_fn_signature,
data_dto=self.resolve_data_dto(),
type_decoders=self.resolve_type_decoders(),
)
return cast("type[SignatureModel]", self._signature_model)
where self._signature_model: type[SignatureModel] | EmptyType = Empty
However the issue with this is that it is really difficult, if not impossible to get mypy to narrow the Empty away - hence why we still need the cast() in the example above.
I want to create a new sentinel for empty, which is valid to be used in a Literal so that it can be narrowed properly:
# types/empty.py
...
class EnumEmpty(Enum):
"""A sentinel class used as placeholder."""
EMPTY = 0
TypeEmpty = Literal[EnumEmpty.EMPTY]
EMPTY: Final = EnumEmpty.EMPTY
My plan would be to replace all internal uses of Empty with EMPTY, so in the above example:
self._signature_model: type[SignatureModel] | TypeEmpty = EMPTY and we'd no longer need to cast() the return of all of these private attrs.
And removal of Empty from public interfaces would have to be a 3.0 thing..
Any thoughts? I know its not ideal that we'd have 4 different "empty things", i.e,. Empty, EMPTY, EmptyType, TypeEmpty (also, I don't like TypeEmpty as a name at all) but IMO its an OK price to pay as long as they are named and name-spaced properly (maybe even an alternate, private for now _empty module for the new ones).