Metadata-Version: 2.3
Name: remus
Version: 0.2.2
Summary: A general purpose toolkit for Python, adding expressive, functional, monadic types inspired by Rust.
Author: IronRomulus
Author-email: IronRomulus <marco.possamai@proton.me>
Requires-Python: >=3.14
Description-Content-Type: text/markdown

# Remus

A general purpose toolkit for Python, adding expressive, functional, monadic types inspired by Rust.

## Installation

Using uv (recommended):

```bash
uv add remus
```

Using pip:

```bash
pip install remus
```

## Examples

### Result

```python
import remus

def divide(a: float, b: float) -> remus.Result[float, str]:
    if b == 0:
        return remus.Err("cannot divide by zero")
    return remus.Ok(a / b)


def main() -> None:
    ok_res = (
        divide(25, 5)
        .map(lambda v: v * 2)
    )

    err_res = (
        divide(10, 0)
        .map_err(lambda v: v.capitalize())
    )

    print(ok_res)
    print(err_res)


if __name__ == "__main__":
    main()
```

Output:

```
Ok(value=10.0)
Err(value="Cannot divide by zero")
```

### Maybe

Here `unwrap_or` returns the wrapped value if `get_user` returns `Some` or a default value.

```python
from collections.abc import Mapping

import remus

users: Mapping[int, str] = {
    1: "Luke Skywalker",
    2: "Leia Organa",
    3: "Han Solo",
}


def get_user(id: int) -> remus.Maybe[str]:
    user = users.get(id)
    if user is None:
        return remus.Nothing
    return remus.Some(user)


def main() -> None:
    some_user = get_user(1).map(lambda v: v.upper())
    nothing_user = get_user(10)
    default_user = nothing_user.unwrap_or("DARTH VADER")

    print(some_user)
    print(nothing_user)
    print(default_user)


if __name__ == "__main__":
    main()
```

Output:

```
Some(value='LUKE SKYWALKER')
Nothing(value=None)
DARTH VADER
```

### Map

Here `unwrap` returns the wrapped value if `users.get` returns `Some` or `panic` (raises `_Panic`).

```python
import remus

users = remus.new_map(
    {
        1: "Luke Skywalker",
        2: "Leia Organa",
        3: "Han Solo",
    }
)


def main() -> None:
    print(users.get(2).map(lambda v: v.lower()).unwrap())
    print(users.get(100).unwrap_or("darth vader"))


if __name__ == "__main__":
    main()
```

Output:

```
leia organa
darth vader
```

## Contribution

If you would like to contribute, please follow the following guidelines:

### Pull Requests (PRs)

Prefix your PRs with the following:

- `chore`: for changes that do not affect the source code or the CI/CD process
- `docs`: for changes to the documentation only
- `feat`: for new features
- `fix`: for bug fixes
- `ops`: for changes that affect the CI/CD workflows only
- `perf`: for changes that affect performance only
- `refactor`: for changes that affect only the maintainability of the code
- `revert`: for changes that revert a previous commit
- `test`: for creating, updating or deleting tests

### Style

#### Imports

Always use module imports, unless the import is from the standard library and the meaning is obvious.

##### External library

As an example, prefer:

```python
import numpy

arr = numpy.array(...)
```

Instead of:

```python
from numpy import array

arr = array(...)
```

##### Standard library

This is OK:

```python
from typing import Protocol

class Something(Protocol): ...
```

#### Patterns

Try to follow good design pattern practices. The most fundamental one that is consistent throughout the codebase is the **static factory pattern**.

Expose `Protocol` classes for consumption by the end user and use static factories with the **new** prefix to allow users to create them. This gives better control over what methods are exposed to the end user.
