Metadata-Version: 2.4
Name: pythonbackport-sentinel
Version: 0.1.0
Summary: Standalone back-port of Python 3.15's `sentinel` builtin.
Project-URL: Repository, https://github.com/pythonbackport/pythonbackport-sentinel
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.6
Description-Content-Type: text/markdown

# sentinel

A back-port of Python 3.15's `sentinel` builtin for older Python versions.

On Python 3.15+, you can write:

```python
MISSING = sentinel("MISSING")
DEFAULT = sentinel("DEFAULT", repr="<DEFAULT>")
```

This package provides the same behaviour on every supported Python
version (3.10+) via a small, focused API.

## Why?

Sentinel objects are an idiomatic way to distinguish *“no value provided”*
from *“value is None”* — useful for default arguments, optional caches,
and any function parameter that needs a tri-state (`unset` / `None` /
real value).

```python
from sentinel import sentinel

MISSING = sentinel("MISSING")

def fetch(key: str, default=None):
    """``default`` may legitimately be ``None``; ``MISSING`` means
    ``fetch`` should compute the fallback itself."""
    ...
```

## Installation

```bash
pip install pythonbackport-sentinel
```

## Usage

### Basic sentinel

```python
from sentinel import sentinel

MISSING = sentinel("MISSING")
MISSING
# -> 'MISSING'
```

### Custom representation

```python
from sentinel import sentinel

MISSING = sentinel("MISSING", repr="<MISSING>")
MISSING
# -> '<MISSING>'
```

### Truthy and unique

```python
from sentinel import sentinel

A = sentinel("A")
B = sentinel("A")      # a *different* object with the same name

assert A                # truthy
assert A is A           # identity equality
assert A is not B       # even with the same name, A != B
```

### Type hints with `|`

```python
from sentinel import sentinel

MISSING = sentinel("MISSING")

def next_value(default: int | MISSING = MISSING):
    ...
```

### Pickle support (module scope)

```python
import pickle
from sentinel import sentinel

PICKLABLE = sentinel("PICKLABLE")
assert pickle.loads(pickle.dumps(PICKLABLE)) is PICKLABLE
```

### Pickle support (class scope)

```python
import pickle
from sentinel import sentinel

class Cls:
    PICKLABLE = sentinel("Cls.PICKLABLE")

assert pickle.loads(pickle.dumps(Cls.PICKLABLE)) is Cls.PICKLABLE
```

## API

### `sentinel(name, /, *, repr=None)`

Return a new unique sentinel object.

- `name` (positional-only, required) — `str`; the sentinel's name, used as
  the default representation.
- `repr` (keyword-only, optional) — `str`; an alternative representation.
  Defaults to `name`.

### Attributes on the returned object

| Attribute   | Read | Write | Description                                             |
|-------------|------|-------|---------------------------------------------------------|
| `__name__`  | ✅   | ❌    | The sentinel's name.                                    |
| `__module__`| ✅   | ✅    | The module that created the sentinel (writable, `str`). |

### Behaviour summary

- Always truthy (`bool(s) is True`).
- Equal only to itself; use the `is` operator.
- Cannot be subclassed.
- `copy.copy(s)` and `copy.deepcopy(s)` both return `s`.
- `s | T` and `T | s` build a usable Union type for annotations.
- Pickling preserves identity when the sentinel is defined at module or
  class scope with a name that matches the variable (or, for class scope,
  the qualified name).

## Python 3.15+ native syntax

When you are running on Python 3.15+, `sentinel` is a true builtin.
The package's own API still works identically, so you can use either:

```python
# Native (Python 3.15+ only):
MISSING = sentinel("MISSING")

# Cross-version equivalent via this package:
from sentinel import sentinel
MISSING = sentinel("MISSING")
```

## Compatibility

- Python 3.10 through 3.15 (uses PEP 604 unions; falls back to
  `typing.Union` on older interpreters).
- No third-party dependencies — uses only the standard library
  (`pickle`, `sys`, `types`).

## Running the tests

```bash
python -m unittest test_sentinel.py -v
```

## Retirement

This project will reach its end-of-life around October 1, 2031 — the
official EOL date of Python 3.15 — and the exact timeline could be
slightly delayed. We plan to ship the final stable release in November
2031. After this release, all support will **cease** and the repository
will be **officially archived**, as this library is developed solely to
bring `sentinel()` compatibility to Python 3.15 and older versions.

## License

MIT
