Skip to main content

Scala-inspired Option type for Python with functional programming utilities

Project description

optionc

A Scala-inspired Option type for Python, providing Some and Nil types for safe handling of nullable values with functional programming patterns.

Installation

pip install optionc

Quick Start

from optionc import Option, Some, Nil

# Create options (Scala-like)
user_email = Option("john@example.com")  # Some("john@example.com")
empty_value = Option(None)               # Nil()

# Direct construction
valid_user = Some("alice@example.com")
no_user = Nil()

# Safe transformations
result = (user_email
          .map(str.upper)
          .filter(lambda s: '@' in s)
          .map(lambda s: s.split('@')[1]))

print(result.get_or_else("unknown"))  # "EXAMPLE.COM"

Core Usage

Creating Options

from optionc import Option, Some, Nil

# From values
Option("hello")     # Some("hello")
Option(42)          # Some(42)  
Option(None)        # Nil()
Option([])          # Some([]) - empty collections are valid

# Direct construction
Some("direct")      # Some("direct")
Nil()              # Nil()

Transformations

# Safe mapping
result = Some(5).map(lambda x: x * 2)  # Some(10)
empty = Nil().map(lambda x: x * 2)     # Nil()

# Filtering
Some(10).filter(lambda x: x > 5)       # Some(10)
Some(3).filter(lambda x: x > 5)        # Nil()

# Flat mapping for nested operations
def divide_by_two(x):
    return Some(x / 2) if x % 2 == 0 else Nil()

Some(8).flat_map(divide_by_two)        # Some(4.0)
Some(5).flat_map(divide_by_two)        # Nil()

Exception Handling

# Normal methods throw exceptions naturally
Some("hello").map(lambda s: s.nonexistent_method())  # AttributeError

# Safe variants return Nil on exceptions  
Some("hello").map_safe(lambda s: s.nonexistent_method())  # Nil()

# Available safe methods: map_safe, flat_map_safe, filter_safe, foreach_safe

Extracting Values

# Get with default
Some("hello").get_or_else("default")    # "hello"
Nil().get_or_else("default")           # "default"

# Get with lazy default
Nil().get_or_else_get(lambda: compute_default())

# Unsafe get (throws on Nil)
Some("hello").get()                    # "hello"
Nil().get()                           # ValueError

Utility Functions

Common patterns for creating Options from various sources:

from optionc import from_callable, from_dict_get, from_getattr

# From function calls
config = from_callable(lambda: load_config_file())  # Some(config) or Nil()

# From dictionary access
user_name = from_dict_get(data, "name", "anonymous")  # Some("anonymous") if key missing

# From object attributes  
email = from_getattr(user, "email", None)  # Some(email) or Nil()

Real-World Example

from optionc import Option, from_dict_get, from_getattr

def process_user_data(data):
    """Safe user data processing pipeline."""
    return (from_dict_get(data, "user")
            .flat_map(lambda user: from_dict_get(user, "profile"))
            .flat_map(lambda profile: from_dict_get(profile, "email"))
            .filter(lambda email: "@" in email)
            .map(lambda email: email.lower())
            .get_or_else("no-email@example.com"))

# Usage
user_data = {
    "user": {
        "profile": {
            "email": "ALICE@EXAMPLE.COM"
        }
    }
}

result = process_user_data(user_data)  # "alice@example.com"
result = process_user_data({})         # "no-email@example.com"

Decorators

Automatically wrap function returns in Options:

@option

Returns Some(result) for non-None values, Nil() for None. Exceptions propagate normally:

from optionc import option

@option
def find_user(user_id: str) -> User:
    return database.get(user_id)  # Returns Option[User]

@option  
def compute_discount(price: float) -> float:
    if price < 0:
        raise ValueError("Invalid price")
    return price * 0.1

# Usage
user = find_user("123")  # Some(User) or Nil()
discount = compute_discount(100.0)  # Some(10.0)
# compute_discount(-1) raises ValueError

@option_safe

Same as @option but catches exceptions and returns Nil():

from optionc import option_safe

@option_safe
def parse_int(s: str) -> int:
    return int(s)  # Some(42) or Nil() on ValueError

@option_safe
def safe_divide(a: int, b: int) -> float:
    return a / b  # Some(result) or Nil() on ZeroDivisionError

# Usage
result = parse_int("42")        # Some(42)
result = parse_int("invalid")   # Nil()
result = safe_divide(10, 2)     # Some(5.0)
result = safe_divide(10, 0)     # Nil()

Decorator Chaining

Decorators work seamlessly with Option methods:

@option_safe
def extract_domain(email: str) -> str:
    if "@" not in email:
        raise ValueError("Invalid email")
    return email.split("@")[1]

# Chain decorated functions
result = (Option("user@EXAMPLE.com")
          .map(str.lower)
          .flat_map(lambda email: extract_domain(email))
          .map(str.upper))

print(result.get())  # "EXAMPLE.COM"

Why use optionc?

Good for:

  • Null safety: Eliminate None checks and AttributeError exceptions
  • Functional pipelines: Chain operations without intermediate null checks
  • API design: Clear contracts about nullable vs non-nullable values
  • Error handling: Graceful degradation with safe method variants

Integration:

  • Type hints: Full generic type support with Option[T]
  • underscorec: Works seamlessly with functional programming patterns
  • Testing: Deterministic behavior makes testing easier

API Reference

Option Methods

# Checking state
.is_defined()           # True for Some, False for Nil
.is_empty()            # False for Some, True for Nil

# Transformations
.map(func)             # Transform value if present
.map_safe(func)        # Transform value, Nil on exception
.flat_map(func)        # Transform and flatten nested Options  
.flat_map_safe(func)   # Flat map, Nil on exception
.filter(predicate)     # Keep value if predicate matches
.filter_safe(pred)     # Filter, Nil on exception

# Extracting values
.get()                 # Get value (throws on Nil)
.get_or_else(default)  # Get value or default
.get_or_else_get(func) # Get value or call function

# Combining
.or_else(alternative)  # Return self if Some, else alternative
.or_else_get(func)     # Return self if Some, else call function

# Side effects  
.foreach(func)         # Execute function if Some
.foreach_safe(func)    # Execute function, ignore exceptions

Contributing

Development setup:

# Clone and setup
git clone https://github.com/carlyou/optionc.git
cd optionc
uv sync --dev

# Run tests
uv run pytest

# Run tests with coverage
uv run pytest --cov=optionc --cov-report=html

Future Improvements

  • Async support: AsyncOption for asynchronous operations
  • Pattern matching: Python 3.10+ match statement integration
  • Serialization: JSON/pickle support for Some/Nil instances

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

optionc-0.1.0.tar.gz (64.5 kB view details)

Uploaded Source

Built Distribution

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

optionc-0.1.0-py3-none-any.whl (9.5 kB view details)

Uploaded Python 3

File details

Details for the file optionc-0.1.0.tar.gz.

File metadata

  • Download URL: optionc-0.1.0.tar.gz
  • Upload date:
  • Size: 64.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.19

File hashes

Hashes for optionc-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e34df504da0d1537b5e9fc4259338be42f4dbdf2e1f46d4ec58915e7b42fc86c
MD5 864e4d5ee65bec9c764c2f9475c2d581
BLAKE2b-256 939c5c3c83c7d0827f5eda7ca498224ef9a203d83759d8fc08a7ab918e835cc5

See more details on using hashes here.

File details

Details for the file optionc-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: optionc-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 9.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.19

File hashes

Hashes for optionc-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8f3f30d433460e97789ac8717189872288fba76305438009746bd38832809cf9
MD5 af289f0adaf3a11586713ebfcccc108d
BLAKE2b-256 96d1126e05468d8eb08d9ad70feb61cc546bcf8fc9230a45ca54f30b3a8e8eab

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