Skip to content

Refactor MISSING_TYPE and MISSING #1873

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
gadenbuie opened this issue Feb 26, 2025 · 2 comments
Open

Refactor MISSING_TYPE and MISSING #1873

gadenbuie opened this issue Feb 26, 2025 · 2 comments

Comments

@gadenbuie
Copy link
Collaborator

We could use extra helpers like

MISSING: MISSING_TYPE = MISSING_TYPE()
DEPRECATED: MISSING_TYPE = MISSING_TYPE()  # A MISSING that communicates deprecation
Maybe = Union[T, MISSING_TYPE]

ListOrTuple = Union[List[T], Tuple[T, ...]]


def is_missing(x: Any) -> TypeIs[MISSING_TYPE]:
    return isinstance(x, MISSING_TYPE)

which would allow something like

# this
arg: Maybe[str] = MISSING

# instead of this
arg: str | MISSING_TYPE = MISSING

This would mostly be for convenience and readability. In particular, MISSING_TYPE feels like a leaked implementation detail.

We could also have a maybe_missing() helper that would look something like this:

def is_missing(x: Any) -> TypeIs[MISSING_TYPE]:
    return isinstance(x, MISSING_TYPE)

@overload
def maybe_missing(x: MISSING_TYPE, default: T) -> T:
    ...

@overload
def maybe_missing(x: T, default: Any) -> T:
    ...

def maybe_missing(x: Any, default: Any) -> Any:
    return default if is_missing(x) else x

Originally posted by @gadenbuie in #1822 (comment)

@wch
Copy link
Collaborator

wch commented Feb 26, 2025

I think the Maybe helper makes sense.

We actually have is_missing and maybe_missing in the code base, but I think they were added before TypeIs was available. (Also, note the types in those implementations, using object instead of Any, and without overloads for maybe_missing())

def is_missing(x: object) -> TypeGuard[MISSING_TYPE]:
return isinstance(x, MISSING_TYPE)
# TypeGuard does not work for `not isinstance(x, MISSING_TYPE)`
# See discussion for `StrictTypeGuard`: https://github.com/python/typing/discussions/1013
# Until then, we need `not_is_missing(x=)` to narrow within an `if` statement
def not_is_missing(x: R | MISSING_TYPE) -> TypeGuard[R]:
return not isinstance(x, MISSING_TYPE)
def all_missing(*args: object) -> TypeGuard[MISSING_TYPE]:
for arg in args:
if not_is_missing(arg):
return False
return True
def maybe_missing(x: M1 | MISSING_TYPE, default: M2) -> M1 | M2:
if isinstance(x, MISSING_TYPE):
return default
return x

A question about DEPRECATED: is that value to be used at runtime for any sort of checking, or is the purpose of it so that the name DEPRECATED is seen by the developer when they look at the function signature?

If it's the latter, then another option is to define a new generic type Deprecated:

MISSING: MISSING_TYPE = MISSING_TYPE()
Deprecated = Union[T, MISSING_TYPE]

# Example usage
def f(x: int, y: Deprecated[int]) -> int: 
    ...

@gadenbuie
Copy link
Collaborator Author

A question about DEPRECATED: is that value to be used at runtime for any sort of checking

Yeah, it's equivalent to MISSING but a different name so that it's clearer to both users and developers why it's missing. I do like the Deprecated[] generic idea, though, I think that'd be a nice addition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants