Metadata-Version: 2.4
Name: punctional
Version: 0.1.0
Summary: A functional programming framework for Python — composable filters, method chaining, and expressive data pipelines with Option and Result monads.
Project-URL: Homepage, https://github.com/peghaz/punctional
Project-URL: Repository, https://github.com/peghaz/punctional
Project-URL: Documentation, https://github.com/peghaz/punctional#readme
Project-URL: Issues, https://github.com/peghaz/punctional/issues
Author-email: Mehdi Peghaz <peghaz@example.com>
License: MIT
License-File: LICENSE
Keywords: composition,data-pipeline,filters,functional-programming,method-chaining,monads,option,pipe-operator,result
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# Punctional

> A functional programming framework for Python — enabling composable filters, method chaining, and expressive data pipelines.

[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)

## 🎯 What is Punctional?

**Punctional** is a lightweight functional programming library that brings powerful functional programming patterns to Python. It allows you to compose operations using an intuitive pipe (`|`) operator, create reusable transformation filters, and apply common functional design patterns like Option and Result monads.

Whether you're building data pipelines, validation logic, or just want cleaner, more declarative code — Punctional provides the building blocks.

## ✨ Key Features

- **🔗 Pipe Operator Chaining** — Chain transformations using the intuitive `|` operator
- **🧱 Composable Filters** — Create reusable, testable transformation units
- **📦 Functional Wrappers** — Wrap native types (`int`, `float`, `str`) for functional operations
- **🎭 Monads** — Built-in `Option` (Some/Nothing) and `Result` (Ok/Error) monads
- **🔧 Extensible** — Easily create custom filters for your domain
- **📋 Dataclass Support** — Make any dataclass functional with the `Functional` mixin

## 📦 Installation

```bash
pip install punctional
```

Or install from source:

```bash
git clone https://github.com/peghaz/punctional.git
cd punctional
pip install -e .
```

## 🚀 Quick Start

```python
from punctional import fint, fstr, Add, Mult, ToUpper, GreaterThan, AndFilter, LessThan

# Arithmetic chaining
result = fint(10) | Add(5) | Mult(2)  # (10 + 5) * 2 = 30

# String transformations
text = fstr("hello") | ToUpper() | Add("!")  # "HELLO!"

# Logical validation
is_valid = fint(42) | AndFilter(GreaterThan(10), LessThan(100))  # True
```

## 📚 Core Concepts

### Filters

A **Filter** is the fundamental building block — a transformation that takes an input and produces an output:

```python
from punctional import Filter, fint

class Square(Filter[int, int]):
    def apply(self, value: int) -> int:
        return value ** 2

# Use it
result = fint(5) | Square()  # 25
```

### Functional Wrappers

Wrap native Python types to enable pipe operations:

| Function | Type | Description |
|----------|------|-------------|
| `fint(x)` | `FunctionalInt` | Functional integer wrapper |
| `ffloat(x)` | `FunctionalFloat` | Functional float wrapper |
| `fstr(x)` | `FunctionalStr` | Functional string wrapper |

### The Pipe Operator

Chain filters using the `|` operator for readable, left-to-right transformations:

```python
# Instead of nested calls:
result = Div(4).apply(Sub(3).apply(Mult(2).apply(Add(5).apply(10))))

# Write declaratively:
result = fint(10) | Add(5) | Mult(2) | Sub(3) | Div(4)
```

## 🔧 Built-in Filters

### Arithmetic Filters

```python
from punctional import Add, Sub, Mult, Div

fint(10) | Add(5)   # 15
fint(10) | Sub(3)   # 7
fint(10) | Mult(2)  # 20
fint(10) | Div(4)   # 2.5
```

### Comparison Filters

```python
from punctional import GreaterThan, LessThan, Equals

fint(42) | GreaterThan(10)  # True
fint(5) | LessThan(10)      # True
fint(42) | Equals(42)       # True
```

### Logical Filters

```python
from punctional import AndFilter, OrFilter, NotFilter, GreaterThan, LessThan, Equals

# AND: all conditions must be true
fint(42) | AndFilter(GreaterThan(10), LessThan(100))  # True

# OR: at least one condition must be true
fint(5) | OrFilter(LessThan(10), GreaterThan(100))    # True

# NOT: negate a condition
fint(5) | NotFilter(Equals(0))                        # True
```

### String Filters

```python
from punctional import ToUpper, ToLower, Contains, fstr

fstr("hello") | ToUpper()              # "HELLO"
fstr("WORLD") | ToLower()              # "world"
fstr("hello world") | Contains("world") # True
fstr("ha") | Mult(3)                   # "hahaha"
```

### List Filters

```python
from punctional import Map, FilterList, Reduce, Mult, GreaterThan

numbers = [1, 2, 3, 4, 5]

# Transform each element
Map(Mult(2)).apply(numbers)  # [2, 4, 6, 8, 10]

# Filter elements
FilterList(GreaterThan(2)).apply(numbers)  # [3, 4, 5]
```

### Composition

Create reusable pipelines with `Compose`:

```python
from punctional import Compose, Mult, Add, fint

# Create a reusable transformation
double_plus_ten = Compose(Mult(2), Add(10))

fint(5) | double_plus_ten   # 20
fint(10) | double_plus_ten  # 30
```

## 🎭 Functional Design Patterns

### Option Monad (Some/Nothing)

Handle nullable values without explicit `None` checks:

```python
from punctional import Some, Nothing, some

# Wrap a value
value = Some(42)
print(value.map(lambda x: x * 2))  # Some(84)
print(value.get_or_else(0))        # 42

# Handle absence
empty = Nothing()
print(empty.map(lambda x: x * 2))  # Nothing
print(empty.get_or_else(0))        # 0

# Auto-convert from potentially None values
result = some(potentially_none_value)  # Returns Nothing if None
```

#### Option Operations

| Method | Description |
|--------|-------------|
| `map(f)` | Transform value if present |
| `flat_map(f)` | Chain operations returning Option |
| `bind(f)` | Alias for flat_map |
| `get_or_else(default)` | Get value or default |
| `get_or_none()` | Get value or None |
| `filter(predicate)` | Return Nothing if predicate fails |
| `or_else(alternative)` | Return alternative if Nothing |
| `is_some()` | Check if value is present |
| `is_nothing()` | Check if value is absent |

### Result Monad (Ok/Error)

Handle operations that can fail with meaningful errors:

```python
from punctional import Ok, Error, try_result

# Successful operation
success = Ok(42)
print(success.map(lambda x: x * 2))  # Ok(84)

# Failed operation
failure = Error("Something went wrong")
print(failure.map(lambda x: x * 2))  # Error("Something went wrong")

# Wrap potentially throwing functions
def divide(a, b):
    return a / b

result = try_result(lambda: divide(10, 0))
# Error(ZeroDivisionError(...))
```

#### Result Operations

| Method | Description |
|--------|-------------|
| `map(f)` | Transform success value |
| `map_error(f)` | Transform error value |
| `flat_map(f)` | Chain operations returning Result |
| `bind(f)` | Alias for flat_map |
| `get_or_else(default)` | Get value or default |
| `is_ok()` | Check if successful |
| `is_error()` | Check if failed |
| `to_option()` | Convert to Option (discards error info) |

## 🏗️ Extending the Framework

### Creating Custom Filters

#### Simple Filter

```python
from punctional import Filter

class Increment(Filter[int, int]):
    def apply(self, value: int) -> int:
        return value + 1
```

#### Parameterized Filter

```python
class Power(Filter[int, int]):
    def __init__(self, exponent: int):
        self.exponent = exponent
    
    def apply(self, value: int) -> int:
        return value ** self.exponent

fint(2) | Power(3)  # 8
```

#### Stateful Filter

```python
class Accumulator(Filter[int, int]):
    def __init__(self, initial: int = 0):
        self.total = initial
    
    def apply(self, value: int) -> int:
        self.total += value
        return self.total

acc = Accumulator()
acc(5)   # 5
acc(10)  # 15
acc(3)   # 18
```

### Functional Dataclasses

Make any dataclass functional with the `Functional` mixin:

```python
from dataclasses import dataclass
from punctional import Functional, Filter

@dataclass
class Point(Functional):
    x: float
    y: float

class ScalePoint(Filter[Point, Point]):
    def __init__(self, factor: float):
        self.factor = factor
    
    def apply(self, point: Point) -> Point:
        return Point(point.x * self.factor, point.y * self.factor)

# Now Point supports piping!
point = Point(3, 4)
scaled = point | ScalePoint(2.5)  # Point(7.5, 10.0)
```

## 📖 Examples

The [examples/](examples/) directory contains comprehensive examples:

| File | Description |
|------|-------------|
| [basics.py](examples/basics.py) | Basic usage and introduction to all features |
| [extending.py](examples/extending.py) | Guide to creating custom filters and domain-specific extensions |
| [data_transformation.py](examples/data_transformation.py) | Advanced patterns: validation pipelines, data transformations |
| [quick_reference.py](examples/quick_reference.py) | Cheat sheet for quick lookup |

### Run the examples:

```bash
python -m examples.basics
python -m examples.extending
python -m examples.data_transformation
```

## 🧪 Common Patterns

### Validation Pipeline

```python
from punctional import AndFilter, Functional, Filter
from dataclasses import dataclass

@dataclass
class Person(Functional):
    name: str
    age: int
    email: str

class ValidateName(Filter[Person, bool]):
    def apply(self, person: Person) -> bool:
        return 1 <= len(person.name) <= 100

class ValidateAge(Filter[Person, bool]):
    def apply(self, person: Person) -> bool:
        return 0 <= person.age <= 150

class ValidateEmail(Filter[Person, bool]):
    def apply(self, person: Person) -> bool:
        return "@" in person.email and "." in person.email

# Use the validation pipeline
person = Person("Alice", 30, "alice@example.com")
is_valid = person | AndFilter(ValidateName(), ValidateAge(), ValidateEmail())  # True
```

### Data Transformation Pipeline

```python
class ApplyBonus(Filter[Person, Person]):
    def __init__(self, percentage: float):
        self.percentage = percentage
    
    def apply(self, person: Person) -> Person:
        return Person(person.name, person.age, person.email)

person | ApplyBonus(10) | PromoteAge() | SaveToDatabase()
```

### List Processing Pipeline

```python
from punctional import FilterList, Map, Mult

class IsEven(Filter[int, bool]):
    def apply(self, value: int) -> bool:
        return value % 2 == 0

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = FilterList(IsEven()).apply(numbers)  # [2, 4, 6, 8, 10]
doubled = Map(Mult(2)).apply(result)          # [4, 8, 12, 16, 20]
```

### Error Handling with Result

```python
from punctional import Result, Ok, Error

def fetch_user(id: int) -> Result[dict, str]:
    if id < 0:
        return Error("Invalid ID")
    return Ok({"id": id, "name": "Alice"})

result = fetch_user(42).map(lambda u: u["name"]).get_or_else("Unknown")  # "Alice"
result = fetch_user(-1).map(lambda u: u["name"]).get_or_else("Unknown")  # "Unknown"
```

## 🎓 Design Principles

1. **Immutability** — Filters don't modify input; they return new values
2. **Composability** — Filters can be combined to create complex transformations
3. **Type Safety** — Generic types help catch errors at development time
4. **Readability** — Pipe operator makes data flow explicit and easy to follow
5. **Extensibility** — Easy to create domain-specific filters

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## 📄 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

---

<p align="center">
  Made with ❤️ for functional programming enthusiasts
</p>
