nofuture 1.2.0
NoFuture: explicit Maybe and Result types. No futures. No exceptions.
Requires Python: >=3.12

# NoFuture

<img src="https://framagit.org/aristofor/nofuture/-/raw/main/NoFuture.svg" alt="NoFuture" width="160">

[![pipeline status](https://framagit.org/aristofor/nofuture/badges/v1.2.0/pipeline.svg)](https://framagit.org/aristofor/nofuture/-/commits/v1.2.0)

- [Changelog](https://framagit.org/aristofor/nofuture/-/blob/main/CHANGELOG.md)
- [License](https://framagit.org/aristofor/nofuture/-/blob/main/LICENSE)

No futures. No exceptions. Just explicit values.

`MayBe` et `Result` pour Python, built en Rust (PyO3 + maturin).

## Manifeste

Tu veux du safe ? Prends un `try/except`.

- pas d'exceptions fantomes
- pas de magie: tout est explicite
- pas de futur promis: on assume `Nothing` / `Err`

```python
from nofuture import MayBe, Result
```

## Installation

```bash
pip install nofuture
```

Dev mode: `maturin develop`

## MayBe

Valeur optionnelle: `Just(value)` ou `Nothing`.

```python
MayBe.just(42)      # Just(42)
MayBe.nothing()     # Nothing

# API
.is_just() / .is_nothing()
.unwrap()                    # raise si Nothing
.expect(msg)                 # raise avec message custom si Nothing
.or_else(default)            # valeur ou default
.to_option()                 # valeur ou None
.map(fn)                     # Just(fn(x)) ou Nothing
.flat_map(fn)                # fn doit retourner MayBe
.match(just=fn, nothing=fn)  # pattern matching
>> fn                        # flat_map
| default                    # or_else
.__len__()                   # 1 si Just, 0 si Nothing
.__iter__()                  # itération sur 0/1 élément
.__bool__()                  # truthiness (Just -> True)
.__repr__()                  # "Just(x)" / "Nothing"
.__class_getitem__()         # support MayBe[T]
```

## Result

Succès ou erreur: `Ok(value)` ou `Err(message, code?, details?)`. La vie en binaire, avec du contexte.

```python
Result.ok(42)
Result.err("not found", code="NOT_FOUND")
Result.err("validation", code="INVALID", details={"field": "name"})
Result.from_dict({"ok": True, "value": 42})  # réciproque de to_dict

# API
.is_ok() / .is_err()
.unwrap()              # raise si Err
.expect(msg)           # raise avec message custom si Err
.unwrap_or(default)    # valeur ou default
.unwrap_err()          # (msg, code, details) ou raise
.to_option()           # valeur ou None
.map(fn)               # Ok(fn(x)) ou Err passthrough
.map_err(fn)           # fn(msg, code, details) -> (msg, code, details)
.flat_map(fn)          # fn doit retourner Result
.and_then(fn)          # alias flat_map
.match(ok=fn, err=fn)  # pattern matching (err reçoit msg, code, details)
.to_dict()             # {"ok": True, "value": ...} ou {"ok": False, "error": ...}
>> fn                  # flat_map
| default              # unwrap_or
.__len__()             # 1 si Ok, 0 si Err
.__iter__()            # itération sur 0/1 élément
.__bool__()            # truthiness (Ok -> True)
.__repr__()            # "Ok(x)" / "Err('msg')"
.__class_getitem__()   # support Result[T, E]
```

## Typage générique

`MayBe` et `Result` supportent la syntaxe générique pour le typage statique :

```python
from nofuture import MayBe, Result
from typing import Any

# MayBe[T] pour les valeurs optionnelles
def find_user(id: int) -> MayBe[User]:
    ...

# Result[T, E] pour les opérations faillibles
MyResult = Result[dict[str, Any], str]
```

## Itération

`MayBe` et `Result` se comportent comme des collections de 0 ou 1 élément :

```python
len(MayBe.just(42))      # 1
len(MayBe.nothing())     # 0

for x in Result.ok("hi"):
    print(x)

value, = MayBe.just("ok")    # unpacking
```

## Exemples

Quelques exemples concrets pour voir `MayBe` et `Result` en action :

- `base.py` : les bases (construction, mapping, chaînage)
- `objects.py` : intégration avec dataclasses et objets métier
- `result.py` : pipelines d'erreurs et sérialisation
- `iteration.py` : itération, unpacking, builtins
- `websocket_app.py` + `websocket_client.py` : mini‑app complète

Tout le détail (dépendances, commandes, payloads, explications) est dans `examples/README.md`.

## Tests

```bash
python3 -m unittest discover -s tests -p 'test_*.py'
```