Skip to main content

Utility types for Python, such as `Result`, `Option`, etc.

Project description

📦 typeric

typeric is a practical type utility toolkit for Python, focused on clarity, safety, and ergonomics. It was originally built to make my own development experience smoother, but I hope it proves useful to others as well.
It currently provides lightweight, pattern-matchable types like Result and Option — inspired by Rust — with plans to include more common type patterns and error-handling abstractions.

pip install typeric

🚀 Features

  • Functional-style Result type
    Ok(value) and Err(error) with powerful .map(), .and_then(), .combine(), .spread() helpers — inspired by Rust’s Result.

  • 🌀 Lightweight Option type
    Some(value) and NONE to handle nullable data safely, with .map(), .unwrap_or(), .is_some() and more.

  • 🔁 Seamless conversion decorators

    • @resulty: Wraps any function to return Result instead of raising exceptions.
    • @optiony: Wraps any function to return Option, converting None or exceptions into NONE.
  • 🧩 Pattern matching support
    Supports Python’s match syntax via __match_args__ for both Ok/Err and Some/NONE.

  • 🔒 Immutable and composable
    Safe and clean method chains using .map(), .combine(), .inspect(), etc.

  • 🔧 Clean type signatures
    Fully typed: Result[T, E] and Option[T] with static analysis and IDE support.

  • 🛠️ Extensible foundation
    Designed for easy extension — more algebraic types (Either, Validated, etc.) can be added naturally.


🔍 Quick Example

Result

from typeric.result import Result, Ok, Err, resulty, resulty_async, optiony, optiony_async

def parse_number(text: str) -> Result[int, str]:
    try:
        return Ok(int(text))
    except ValueError:
        return Err("Not a number")

match parse_number("42"):
    case Ok(value):
        print("Parsed:", value)
    case Err(error):
        print("Failed:", error)

# let function return Result[T,str]
@resulty
def add(x: int, y: int) -> int:
    return x + y

res = add(1, 2)
if res.is_ok():
    print("Result:", res.unwrap())
else:
    print("Error:", res.err)

# let async function return Result[T,str]
@resulty_async
async def async_add(x: int, y: int) -> int:
    return x + y

res = await async_add(1, 2)
if res.is_ok():
    print("Result:", res.unwrap())
else:
    print("Error:", res.err)

def func_a(x: int) -> Result[int, str]:
    if x < 0:
        return Err("negative input")
    return Ok(x * 2)


@spreadable
def func_b(y: int) -> Result[int, str]:
    a = func_a(y).spread()
    return Ok(a + 1)


def test_func_b_success():
    assert func_b(5) == Ok(11)  # 5*2=10 +1=11


def test_func_b_propagate_error():
    assert func_b(-2) == Err("negative input")

def validate_username(username: str) -> Result[str, str]:
    if username.strip():
        return Ok(username)
    return Err("Username is empty")


def validate_age(age: int) -> Result[int, str]:
    if age > 0:
        return Ok(age)
    return Err("Age must > 0")


def validate_email(email: str) -> Result[str, str]:
    if "@" in email:
        return Ok(email)
    return Err("Invalid email")


# ✅ results combine
def validate_user_data(
    username: str, age: int, email: str
) -> Result[tuple[tuple[str, int], str], str]:
    return (
        validate_username(username)
        .combine(validate_age(age))
        .combine(validate_email(email))
    )


result1 = validate_user_data("alice", 30, "alice@example.com")
print(result1)  # Ok((('alice', 30), 'alice@example.com'))

result2 = validate_user_data("", -5, "invalid-email")
print(result2.errs)  # Err(['Username is empty', 'Age must > 0', 'Invalid email'])

Option

from typeric.option import Option, Some, NONE

def maybe_get(index: int, items: list[str]) -> Option[str]:
    if 0 <= index < len(items):
        return Some(items[index])
    return NONE

match maybe_get(1, ["a", "b", "c"]):
    case Some(value):
        print("Got:", value)
    case NONE:
        print("Nothing found")

@optiony
def get_number(x: int) -> int | None:
    if x > 0:
        return x
    return None

@optiony_async
async def fetch_data(flag: bool) -> str | None:
    if flag:
        return "data"
    return None

✅ Test

Run tests with:

uv run pytest -v

📦 Roadmap

  • Async Result
  • OptionResult combinators
  • Try, Either, NonEmptyList, etc.

📄 License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typeric-0.1.6.tar.gz (28.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

typeric-0.1.6-py3-none-any.whl (7.1 kB view details)

Uploaded Python 3

File details

Details for the file typeric-0.1.6.tar.gz.

File metadata

  • Download URL: typeric-0.1.6.tar.gz
  • Upload date:
  • Size: 28.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.8

File hashes

Hashes for typeric-0.1.6.tar.gz
Algorithm Hash digest
SHA256 df943a5019ad8688bb700abb5a217f0a292530c946ef58c2b4adc316d6990b1e
MD5 b66d0371a9b336dab3855d2a47194ff4
BLAKE2b-256 12bfbf592a5e3fcbdee110d604955cb284502e90e8455d18cde2a10b0b7d1516

See more details on using hashes here.

File details

Details for the file typeric-0.1.6-py3-none-any.whl.

File metadata

  • Download URL: typeric-0.1.6-py3-none-any.whl
  • Upload date:
  • Size: 7.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.8

File hashes

Hashes for typeric-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 f6d37fb4eceb82d7a66be4f279acb311c22f6b92bcc3d3337c7340567e5e9a51
MD5 3119b77c27c950e3f1493bb0c382480c
BLAKE2b-256 773f0b322d2c7ca7706b60c2b44ad3df4475072dc406e9e93963d6090b9122f8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page