Metadata-Version: 2.4
Name: maybe-missing
Version: 0.1
Summary: A tiny typed sentinel for distinguishing None from not provided
Author-email: Alexander Tikhonov <random.gauss@gmail.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/Fatal1ty/maybe-missing
Keywords: typing,sentinel,optional,api,dataclass,maybe
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Development Status :: 5 - Production/Stable
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# maybe-missing

`maybe-missing` is a tiny typed helper for one annoyingly real distinction:

- `None` means “the caller explicitly sent null”
- `MISSING` means “the caller did not send this value at all”

That difference matters in patch APIs, dataclasses, settings layers, and anywhere a database column is nullable but an omitted field should mean “leave it alone”.

## Why this exists

Python’s `Optional[T]` answers only one question: can the value be `None`?

It does **not** answer the other question: was there a value in the first place?

That becomes awkward fast:

- JSON `null` is a valid payload value
- a nullable Postgres column may need to be set to `NULL` intentionally
- omitting a field in a PATCH request should usually mean “don’t update it”
- a dataclass default should sometimes mean “missing”, not “defaulted to null”

So this package gives you exactly two public names:

- `Maybe[T]`
- `MISSING`

And then gets out of your way.

## Installation

```bash
pip install maybe-missing
```

## Usage

```python
from maybe_missing import Maybe, MISSING


def update_display_name(display_name: Maybe[str | None] = MISSING) -> None:
    if display_name is MISSING:
        print("leave the existing value alone")
    elif display_name is None:
        print("explicitly store NULL")
    else:
        print(f"store {display_name!r}")
```

## Dataclass example

```python
from dataclasses import dataclass

from maybe_missing import Maybe, MISSING


@dataclass(slots=True)
class UserPatch:
    nickname: Maybe[str | None] = MISSING
    bio: Maybe[str | None] = MISSING


def apply_patch(patch: UserPatch) -> None:
    if patch.nickname is not MISSING:
        # update nickname to a string or to NULL
        ...

    if patch.bio is not MISSING:
        # update bio to a string or to NULL
        ...
```

## API example

Imagine a request body for partial updates:

```json
{
  "nickname": null
}
```

This should mean:

- `nickname` was provided
- its value is explicitly `null`
- the server should write `NULL`

While this body:

```json
{}
```

should mean:

- `nickname` was not provided
- keep the current value as is

`Maybe[T]` helps model that cleanly in Python code.

## There are only a couple of lines though

Yes.

There are only a couple of lines.

And that’s exactly why it’s more pleasant not to duplicate them across your projects and just do:

```python
from maybe_missing import Maybe, MISSING
```

Isn’t it?

## Typing

The package includes `py.typed`, so type checkers and IDEs can treat it as a typed distribution.

## Compatibility

This package currently targets Python 3.10+.
