# optionc
A Scala-inspired Option type for Python, providing `Some` and `Nil` types for safe handling of nullable values with functional programming patterns.
## Installation
```bash
pip install optionc
```
## Quick Start
```python
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
```python
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
```python
# 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
```python
# 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
```python
# 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:
```python
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
```python
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:
```python
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()`:
```python
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:
```python
@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
```python
# 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:
```bash
# 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
Raw data
{
"_id": null,
"home_page": null,
"name": "optionc",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "functional, maybe, monads, option, programming, scala",
"author": "Carl You",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/93/9c/5c3c83c7d0827f5eda7ca498224ef9a203d83759d8fc08a7ab918e835cc5/optionc-0.1.0.tar.gz",
"platform": null,
"description": "# optionc\n\nA Scala-inspired Option type for Python, providing `Some` and `Nil` types for safe handling of nullable values with functional programming patterns.\n\n## Installation\n\n```bash\npip install optionc\n```\n\n## Quick Start\n\n```python\nfrom optionc import Option, Some, Nil\n\n# Create options (Scala-like)\nuser_email = Option(\"john@example.com\") # Some(\"john@example.com\")\nempty_value = Option(None) # Nil()\n\n# Direct construction\nvalid_user = Some(\"alice@example.com\")\nno_user = Nil()\n\n# Safe transformations\nresult = (user_email\n .map(str.upper)\n .filter(lambda s: '@' in s)\n .map(lambda s: s.split('@')[1]))\n\nprint(result.get_or_else(\"unknown\")) # \"EXAMPLE.COM\"\n```\n\n## Core Usage\n\n### Creating Options\n\n```python\nfrom optionc import Option, Some, Nil\n\n# From values\nOption(\"hello\") # Some(\"hello\")\nOption(42) # Some(42) \nOption(None) # Nil()\nOption([]) # Some([]) - empty collections are valid\n\n# Direct construction\nSome(\"direct\") # Some(\"direct\")\nNil() # Nil()\n```\n\n### Transformations\n\n```python\n# Safe mapping\nresult = Some(5).map(lambda x: x * 2) # Some(10)\nempty = Nil().map(lambda x: x * 2) # Nil()\n\n# Filtering\nSome(10).filter(lambda x: x > 5) # Some(10)\nSome(3).filter(lambda x: x > 5) # Nil()\n\n# Flat mapping for nested operations\ndef divide_by_two(x):\n return Some(x / 2) if x % 2 == 0 else Nil()\n\nSome(8).flat_map(divide_by_two) # Some(4.0)\nSome(5).flat_map(divide_by_two) # Nil()\n```\n\n### Exception Handling\n\n```python\n# Normal methods throw exceptions naturally\nSome(\"hello\").map(lambda s: s.nonexistent_method()) # AttributeError\n\n# Safe variants return Nil on exceptions \nSome(\"hello\").map_safe(lambda s: s.nonexistent_method()) # Nil()\n\n# Available safe methods: map_safe, flat_map_safe, filter_safe, foreach_safe\n```\n\n### Extracting Values\n\n```python\n# Get with default\nSome(\"hello\").get_or_else(\"default\") # \"hello\"\nNil().get_or_else(\"default\") # \"default\"\n\n# Get with lazy default\nNil().get_or_else_get(lambda: compute_default())\n\n# Unsafe get (throws on Nil)\nSome(\"hello\").get() # \"hello\"\nNil().get() # ValueError\n```\n\n## Utility Functions\n\nCommon patterns for creating Options from various sources:\n\n```python\nfrom optionc import from_callable, from_dict_get, from_getattr\n\n# From function calls\nconfig = from_callable(lambda: load_config_file()) # Some(config) or Nil()\n\n# From dictionary access\nuser_name = from_dict_get(data, \"name\", \"anonymous\") # Some(\"anonymous\") if key missing\n\n# From object attributes \nemail = from_getattr(user, \"email\", None) # Some(email) or Nil()\n```\n\n### Real-World Example\n\n```python\nfrom optionc import Option, from_dict_get, from_getattr\n\ndef process_user_data(data):\n \"\"\"Safe user data processing pipeline.\"\"\"\n return (from_dict_get(data, \"user\")\n .flat_map(lambda user: from_dict_get(user, \"profile\"))\n .flat_map(lambda profile: from_dict_get(profile, \"email\"))\n .filter(lambda email: \"@\" in email)\n .map(lambda email: email.lower())\n .get_or_else(\"no-email@example.com\"))\n\n# Usage\nuser_data = {\n \"user\": {\n \"profile\": {\n \"email\": \"ALICE@EXAMPLE.COM\"\n }\n }\n}\n\nresult = process_user_data(user_data) # \"alice@example.com\"\nresult = process_user_data({}) # \"no-email@example.com\"\n```\n\n## Decorators\n\nAutomatically wrap function returns in Options:\n\n### @option\n\nReturns `Some(result)` for non-None values, `Nil()` for None. Exceptions propagate normally:\n\n```python\nfrom optionc import option\n\n@option\ndef find_user(user_id: str) -> User:\n return database.get(user_id) # Returns Option[User]\n\n@option \ndef compute_discount(price: float) -> float:\n if price < 0:\n raise ValueError(\"Invalid price\")\n return price * 0.1\n\n# Usage\nuser = find_user(\"123\") # Some(User) or Nil()\ndiscount = compute_discount(100.0) # Some(10.0)\n# compute_discount(-1) raises ValueError\n```\n\n### @option_safe\n\nSame as `@option` but catches exceptions and returns `Nil()`:\n\n```python\nfrom optionc import option_safe\n\n@option_safe\ndef parse_int(s: str) -> int:\n return int(s) # Some(42) or Nil() on ValueError\n\n@option_safe\ndef safe_divide(a: int, b: int) -> float:\n return a / b # Some(result) or Nil() on ZeroDivisionError\n\n# Usage\nresult = parse_int(\"42\") # Some(42)\nresult = parse_int(\"invalid\") # Nil()\nresult = safe_divide(10, 2) # Some(5.0)\nresult = safe_divide(10, 0) # Nil()\n```\n\n### Decorator Chaining\n\nDecorators work seamlessly with Option methods:\n\n```python\n@option_safe\ndef extract_domain(email: str) -> str:\n if \"@\" not in email:\n raise ValueError(\"Invalid email\")\n return email.split(\"@\")[1]\n\n# Chain decorated functions\nresult = (Option(\"user@EXAMPLE.com\")\n .map(str.lower)\n .flat_map(lambda email: extract_domain(email))\n .map(str.upper))\n\nprint(result.get()) # \"EXAMPLE.COM\"\n```\n\n## Why use optionc?\n\n### Good for:\n- **Null safety**: Eliminate `None` checks and `AttributeError` exceptions\n- **Functional pipelines**: Chain operations without intermediate null checks \n- **API design**: Clear contracts about nullable vs non-nullable values\n- **Error handling**: Graceful degradation with safe method variants\n\n### Integration:\n- **Type hints**: Full generic type support with `Option[T]`\n- **underscorec**: Works seamlessly with functional programming patterns\n- **Testing**: Deterministic behavior makes testing easier\n\n## API Reference\n\n### Option Methods\n\n```python\n# Checking state\n.is_defined() # True for Some, False for Nil\n.is_empty() # False for Some, True for Nil\n\n# Transformations\n.map(func) # Transform value if present\n.map_safe(func) # Transform value, Nil on exception\n.flat_map(func) # Transform and flatten nested Options \n.flat_map_safe(func) # Flat map, Nil on exception\n.filter(predicate) # Keep value if predicate matches\n.filter_safe(pred) # Filter, Nil on exception\n\n# Extracting values\n.get() # Get value (throws on Nil)\n.get_or_else(default) # Get value or default\n.get_or_else_get(func) # Get value or call function\n\n# Combining\n.or_else(alternative) # Return self if Some, else alternative\n.or_else_get(func) # Return self if Some, else call function\n\n# Side effects \n.foreach(func) # Execute function if Some\n.foreach_safe(func) # Execute function, ignore exceptions\n```\n\n## Contributing\n\nDevelopment setup:\n\n```bash\n# Clone and setup\ngit clone https://github.com/carlyou/optionc.git\ncd optionc\nuv sync --dev\n\n# Run tests\nuv run pytest\n\n# Run tests with coverage\nuv run pytest --cov=optionc --cov-report=html\n```\n\n## Future Improvements\n\n- **Async support**: `AsyncOption` for asynchronous operations\n- **Pattern matching**: Python 3.10+ match statement integration \n- **Serialization**: JSON/pickle support for Some/Nil instances\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Scala-inspired Option type for Python with functional programming utilities",
"version": "0.1.0",
"project_urls": {
"Homepage": "https://github.com/your-username/optionc",
"Issues": "https://github.com/your-username/optionc/issues",
"Repository": "https://github.com/your-username/optionc.git"
},
"split_keywords": [
"functional",
" maybe",
" monads",
" option",
" programming",
" scala"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "96d1126e05468d8eb08d9ad70feb61cc546bcf8fc9230a45ca54f30b3a8e8eab",
"md5": "af289f0adaf3a11586713ebfcccc108d",
"sha256": "8f3f30d433460e97789ac8717189872288fba76305438009746bd38832809cf9"
},
"downloads": -1,
"filename": "optionc-0.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "af289f0adaf3a11586713ebfcccc108d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 9546,
"upload_time": "2025-08-05T06:49:40",
"upload_time_iso_8601": "2025-08-05T06:49:40.426378Z",
"url": "https://files.pythonhosted.org/packages/96/d1/126e05468d8eb08d9ad70feb61cc546bcf8fc9230a45ca54f30b3a8e8eab/optionc-0.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "939c5c3c83c7d0827f5eda7ca498224ef9a203d83759d8fc08a7ab918e835cc5",
"md5": "864e4d5ee65bec9c764c2f9475c2d581",
"sha256": "e34df504da0d1537b5e9fc4259338be42f4dbdf2e1f46d4ec58915e7b42fc86c"
},
"downloads": -1,
"filename": "optionc-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "864e4d5ee65bec9c764c2f9475c2d581",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 64541,
"upload_time": "2025-08-05T06:49:41",
"upload_time_iso_8601": "2025-08-05T06:49:41.874257Z",
"url": "https://files.pythonhosted.org/packages/93/9c/5c3c83c7d0827f5eda7ca498224ef9a203d83759d8fc08a7ab918e835cc5/optionc-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-05 06:49:41",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "your-username",
"github_project": "optionc",
"github_not_found": true,
"lcname": "optionc"
}