punctional 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- punctional/__init__.py +1 -1
- punctional-0.1.1.dist-info/METADATA +426 -0
- {punctional-0.1.0.dist-info → punctional-0.1.1.dist-info}/RECORD +5 -5
- punctional-0.1.0.dist-info/METADATA +0 -445
- {punctional-0.1.0.dist-info → punctional-0.1.1.dist-info}/WHEEL +0 -0
- {punctional-0.1.0.dist-info → punctional-0.1.1.dist-info}/licenses/LICENSE +0 -0
punctional/__init__.py
CHANGED
|
@@ -13,7 +13,7 @@ from .string import ToUpper, ToLower, Contains
|
|
|
13
13
|
from .list_filters import Map, FilterList, Reduce
|
|
14
14
|
from .monads import Option, Some, Nothing, Result, Ok, Error, some, try_result
|
|
15
15
|
|
|
16
|
-
__version__ = "0.1.
|
|
16
|
+
__version__ = "0.1.1"
|
|
17
17
|
|
|
18
18
|
__all__ = [
|
|
19
19
|
# Core
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: punctional
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A functional programming framework for Python — composable filters, method chaining, and expressive data pipelines with Option and Result monads.
|
|
5
|
+
Project-URL: Homepage, https://github.com/peghaz/punctional
|
|
6
|
+
Project-URL: Repository, https://github.com/peghaz/punctional
|
|
7
|
+
Project-URL: Documentation, https://github.com/peghaz/punctional#readme
|
|
8
|
+
Project-URL: Issues, https://github.com/peghaz/punctional/issues
|
|
9
|
+
Author-email: Mehdi Peghaz <peghaz@example.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: composition,data-pipeline,filters,functional-programming,method-chaining,monads,option,pipe-operator,result
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Punctional
|
|
26
|
+
|
|
27
|
+
> Functional programming for Python — monads, composable filters, and expressive data pipelines.
|
|
28
|
+
|
|
29
|
+
[](https://www.python.org/downloads/)
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
|
|
32
|
+
## What is Punctional?
|
|
33
|
+
|
|
34
|
+
**Punctional** brings robust functional programming patterns to Python. It provides:
|
|
35
|
+
|
|
36
|
+
- **Monads** for safe error handling and null-safety (`Option`, `Result`)
|
|
37
|
+
- **Composable filters** that chain with the pipe (`|`) operator
|
|
38
|
+
- **Functional wrappers** for native types enabling method chaining
|
|
39
|
+
|
|
40
|
+
No dependencies. Pure Python. Type-safe.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install punctional
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or from source:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/peghaz/punctional.git
|
|
52
|
+
cd punctional
|
|
53
|
+
pip install -e .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Core Features
|
|
59
|
+
|
|
60
|
+
### 1. Option Monad — Safe Null Handling
|
|
61
|
+
|
|
62
|
+
The `Option` type represents a value that may or may not exist. Use `Some` to wrap a value and `Nothing` to represent absence — eliminating `None` checks and `AttributeError` exceptions.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from punctional import Some, Nothing, some
|
|
66
|
+
|
|
67
|
+
# Wrap existing values
|
|
68
|
+
user_name = Some("Alice")
|
|
69
|
+
print(user_name.map(str.upper)) # Some('ALICE')
|
|
70
|
+
print(user_name.get_or_else("Unknown")) # 'Alice'
|
|
71
|
+
|
|
72
|
+
# Handle missing values safely
|
|
73
|
+
missing = Nothing()
|
|
74
|
+
print(missing.map(str.upper)) # Nothing
|
|
75
|
+
print(missing.get_or_else("Unknown")) # 'Unknown'
|
|
76
|
+
|
|
77
|
+
# Auto-convert from potentially None values
|
|
78
|
+
def find_user(user_id):
|
|
79
|
+
return {"alice": "Alice"}.get(user_id)
|
|
80
|
+
|
|
81
|
+
result = some(find_user("bob")) # Nothing (because get returns None)
|
|
82
|
+
result = some(find_user("alice")) # Some('Alice')
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Option Methods:**
|
|
86
|
+
|
|
87
|
+
| Method | Description |
|
|
88
|
+
|--------|-------------|
|
|
89
|
+
| `map(f)` | Transform the value if present |
|
|
90
|
+
| `flat_map(f)` / `bind(f)` | Chain operations that return `Option` |
|
|
91
|
+
| `filter(predicate)` | Return `Nothing` if predicate fails |
|
|
92
|
+
| `get_or_else(default)` | Get value or return default |
|
|
93
|
+
| `get_or_none()` | Get value or `None` |
|
|
94
|
+
| `or_else(alternative)` | Return alternative `Option` if `Nothing` |
|
|
95
|
+
| `is_some()` / `is_nothing()` | Check presence |
|
|
96
|
+
|
|
97
|
+
**Chaining with Option:**
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from punctional import Some, Nothing
|
|
101
|
+
|
|
102
|
+
def parse_int(s: str) -> Option[int]:
|
|
103
|
+
try:
|
|
104
|
+
return Some(int(s))
|
|
105
|
+
except ValueError:
|
|
106
|
+
return Nothing()
|
|
107
|
+
|
|
108
|
+
def double(x: int) -> Option[int]:
|
|
109
|
+
return Some(x * 2)
|
|
110
|
+
|
|
111
|
+
# Chain operations safely
|
|
112
|
+
result = Some("42").flat_map(parse_int).flat_map(double) # Some(84)
|
|
113
|
+
result = Some("abc").flat_map(parse_int).flat_map(double) # Nothing
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### 2. Result Monad — Explicit Error Handling
|
|
119
|
+
|
|
120
|
+
The `Result` type represents either success (`Ok`) or failure (`Error`). Unlike exceptions, errors are explicit in the type signature and must be handled.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from punctional import Ok, Error, try_result
|
|
124
|
+
|
|
125
|
+
# Explicit success and failure
|
|
126
|
+
success = Ok(42)
|
|
127
|
+
failure = Error("Something went wrong")
|
|
128
|
+
|
|
129
|
+
print(success.map(lambda x: x * 2)) # Ok(84)
|
|
130
|
+
print(failure.map(lambda x: x * 2)) # Error('Something went wrong')
|
|
131
|
+
|
|
132
|
+
print(success.get_or_else(0)) # 42
|
|
133
|
+
print(failure.get_or_else(0)) # 0
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Wrapping exceptions with `try_result`:**
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from punctional import try_result
|
|
140
|
+
|
|
141
|
+
def divide(a, b):
|
|
142
|
+
return a / b
|
|
143
|
+
|
|
144
|
+
result = try_result(lambda: divide(10, 2)) # Ok(5.0)
|
|
145
|
+
result = try_result(lambda: divide(10, 0)) # Error(ZeroDivisionError(...))
|
|
146
|
+
|
|
147
|
+
# With custom error handler
|
|
148
|
+
result = try_result(
|
|
149
|
+
lambda: divide(10, 0),
|
|
150
|
+
error_handler=lambda e: f"Division failed: {e}"
|
|
151
|
+
) # Error('Division failed: division by zero')
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Result Methods:**
|
|
155
|
+
|
|
156
|
+
| Method | Description |
|
|
157
|
+
|--------|-------------|
|
|
158
|
+
| `map(f)` | Transform success value |
|
|
159
|
+
| `map_error(f)` | Transform error value |
|
|
160
|
+
| `flat_map(f)` / `bind(f)` | Chain operations returning `Result` |
|
|
161
|
+
| `get_or_else(default)` | Get value or default |
|
|
162
|
+
| `is_ok()` / `is_error()` | Check success/failure |
|
|
163
|
+
| `to_option()` | Convert to `Option` (discards error info) |
|
|
164
|
+
|
|
165
|
+
**Railway-oriented programming:**
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from punctional import Result, Ok, Error
|
|
169
|
+
|
|
170
|
+
def validate_age(age: int) -> Result[int, str]:
|
|
171
|
+
if age < 0:
|
|
172
|
+
return Error("Age cannot be negative")
|
|
173
|
+
if age > 150:
|
|
174
|
+
return Error("Age seems unrealistic")
|
|
175
|
+
return Ok(age)
|
|
176
|
+
|
|
177
|
+
def validate_name(name: str) -> Result[str, str]:
|
|
178
|
+
if not name.strip():
|
|
179
|
+
return Error("Name cannot be empty")
|
|
180
|
+
return Ok(name.strip())
|
|
181
|
+
|
|
182
|
+
# Chain validations
|
|
183
|
+
age_result = validate_age(25).map(lambda a: f"Age: {a}") # Ok('Age: 25')
|
|
184
|
+
age_result = validate_age(-5).map(lambda a: f"Age: {a}") # Error('Age cannot be negative')
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### 3. Pipe Operator & Filters
|
|
190
|
+
|
|
191
|
+
Chain transformations using the `|` operator for readable, left-to-right data flow.
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from punctional import fint, fstr, ffloat, Add, Mult, Sub, Div, ToUpper
|
|
195
|
+
|
|
196
|
+
# Arithmetic chaining
|
|
197
|
+
result = fint(10) | Add(5) | Mult(2) | Sub(3) # (10 + 5) * 2 - 3 = 27
|
|
198
|
+
|
|
199
|
+
# String transformations
|
|
200
|
+
text = fstr("hello") | ToUpper() | Add(" WORLD!") # 'HELLO WORLD!'
|
|
201
|
+
|
|
202
|
+
# Float operations
|
|
203
|
+
value = ffloat(10.0) | Div(4) | Mult(2) # 5.0
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Functional Wrappers:**
|
|
207
|
+
|
|
208
|
+
| Function | Type | Description |
|
|
209
|
+
|----------|------|-------------|
|
|
210
|
+
| `fint(x)` | `FunctionalInt` | Enables piping for integers |
|
|
211
|
+
| `ffloat(x)` | `FunctionalFloat` | Enables piping for floats |
|
|
212
|
+
| `fstr(x)` | `FunctionalStr` | Enables piping for strings |
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### 4. Built-in Filters
|
|
217
|
+
|
|
218
|
+
**Arithmetic:**
|
|
219
|
+
```python
|
|
220
|
+
from punctional import fint, Add, Sub, Mult, Div
|
|
221
|
+
|
|
222
|
+
fint(10) | Add(5) # 15
|
|
223
|
+
fint(10) | Sub(3) # 7
|
|
224
|
+
fint(10) | Mult(2) # 20
|
|
225
|
+
fint(10) | Div(4) # 2.5
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Comparison:**
|
|
229
|
+
```python
|
|
230
|
+
from punctional import fint, GreaterThan, LessThan, Equals
|
|
231
|
+
|
|
232
|
+
fint(42) | GreaterThan(10) # True
|
|
233
|
+
fint(5) | LessThan(10) # True
|
|
234
|
+
fint(42) | Equals(42) # True
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Logical:**
|
|
238
|
+
```python
|
|
239
|
+
from punctional import fint, AndFilter, OrFilter, NotFilter, GreaterThan, LessThan
|
|
240
|
+
|
|
241
|
+
# All conditions must pass
|
|
242
|
+
fint(42) | AndFilter(GreaterThan(10), LessThan(100)) # True
|
|
243
|
+
|
|
244
|
+
# At least one condition must pass
|
|
245
|
+
fint(5) | OrFilter(LessThan(3), GreaterThan(3)) # True
|
|
246
|
+
|
|
247
|
+
# Negate a condition
|
|
248
|
+
fint(5) | NotFilter(Equals(0)) # True
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**String:**
|
|
252
|
+
```python
|
|
253
|
+
from punctional import fstr, ToUpper, ToLower, Contains, Mult
|
|
254
|
+
|
|
255
|
+
fstr("hello") | ToUpper() # 'HELLO'
|
|
256
|
+
fstr("WORLD") | ToLower() # 'world'
|
|
257
|
+
fstr("hello world") | Contains("world") # True
|
|
258
|
+
fstr("ha") | Mult(3) # 'hahaha'
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**List Operations:**
|
|
262
|
+
```python
|
|
263
|
+
from punctional import Map, FilterList, Mult, GreaterThan
|
|
264
|
+
|
|
265
|
+
numbers = [1, 2, 3, 4, 5]
|
|
266
|
+
|
|
267
|
+
Map(Mult(2)).apply(numbers) # [2, 4, 6, 8, 10]
|
|
268
|
+
FilterList(GreaterThan(2)).apply(numbers) # [3, 4, 5]
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
### 5. Composition
|
|
274
|
+
|
|
275
|
+
Create reusable pipelines with `Compose`:
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
from punctional import Compose, Mult, Add, fint
|
|
279
|
+
|
|
280
|
+
# Define a reusable transformation
|
|
281
|
+
double_then_add_ten = Compose(Mult(2), Add(10))
|
|
282
|
+
|
|
283
|
+
fint(5) | double_then_add_ten # 20 (5 * 2 + 10)
|
|
284
|
+
fint(10) | double_then_add_ten # 30 (10 * 2 + 10)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### 6. Custom Filters
|
|
290
|
+
|
|
291
|
+
Create your own filters by extending the `Filter` base class:
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from punctional import Filter, fint
|
|
295
|
+
|
|
296
|
+
class Square(Filter[int, int]):
|
|
297
|
+
def apply(self, value: int) -> int:
|
|
298
|
+
return value ** 2
|
|
299
|
+
|
|
300
|
+
class Power(Filter[int, int]):
|
|
301
|
+
def __init__(self, exponent: int):
|
|
302
|
+
self.exponent = exponent
|
|
303
|
+
|
|
304
|
+
def apply(self, value: int) -> int:
|
|
305
|
+
return value ** self.exponent
|
|
306
|
+
|
|
307
|
+
fint(5) | Square() # 25
|
|
308
|
+
fint(2) | Power(10) # 1024
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### 7. Functional Dataclasses
|
|
314
|
+
|
|
315
|
+
Add the `Functional` mixin to any dataclass to enable piping:
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
from dataclasses import dataclass
|
|
319
|
+
from punctional import Functional, Filter
|
|
320
|
+
|
|
321
|
+
@dataclass
|
|
322
|
+
class Point(Functional):
|
|
323
|
+
x: float
|
|
324
|
+
y: float
|
|
325
|
+
|
|
326
|
+
class Scale(Filter[Point, Point]):
|
|
327
|
+
def __init__(self, factor: float):
|
|
328
|
+
self.factor = factor
|
|
329
|
+
|
|
330
|
+
def apply(self, p: Point) -> Point:
|
|
331
|
+
return Point(p.x * self.factor, p.y * self.factor)
|
|
332
|
+
|
|
333
|
+
class Translate(Filter[Point, Point]):
|
|
334
|
+
def __init__(self, dx: float, dy: float):
|
|
335
|
+
self.dx, self.dy = dx, dy
|
|
336
|
+
|
|
337
|
+
def apply(self, p: Point) -> Point:
|
|
338
|
+
return Point(p.x + self.dx, p.y + self.dy)
|
|
339
|
+
|
|
340
|
+
# Chain transformations on custom types
|
|
341
|
+
point = Point(3, 4)
|
|
342
|
+
result = point | Scale(2) | Translate(10, 10) # Point(16, 18)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Complete API Reference
|
|
348
|
+
|
|
349
|
+
### Monads
|
|
350
|
+
|
|
351
|
+
| Type | Description |
|
|
352
|
+
|------|-------------|
|
|
353
|
+
| `Option[T]` | Abstract base for optional values |
|
|
354
|
+
| `Some(value)` | Contains a value |
|
|
355
|
+
| `Nothing()` | Represents absence |
|
|
356
|
+
| `some(value)` | Creates `Some` or `Nothing` based on `None` check |
|
|
357
|
+
| `Result[T, E]` | Abstract base for success/failure |
|
|
358
|
+
| `Ok(value)` | Successful result |
|
|
359
|
+
| `Error(error)` | Failed result |
|
|
360
|
+
| `try_result(fn)` | Wraps a function, catching exceptions as `Error` |
|
|
361
|
+
|
|
362
|
+
### Filters
|
|
363
|
+
|
|
364
|
+
| Filter | Input → Output | Description |
|
|
365
|
+
|--------|----------------|-------------|
|
|
366
|
+
| `Add(n)` | `number → number` | Addition |
|
|
367
|
+
| `Sub(n)` | `number → number` | Subtraction |
|
|
368
|
+
| `Mult(n)` | `number → number` | Multiplication |
|
|
369
|
+
| `Div(n)` | `number → number` | Division |
|
|
370
|
+
| `GreaterThan(n)` | `number → bool` | Greater than comparison |
|
|
371
|
+
| `LessThan(n)` | `number → bool` | Less than comparison |
|
|
372
|
+
| `Equals(n)` | `any → bool` | Equality check |
|
|
373
|
+
| `AndFilter(*filters)` | `T → bool` | Logical AND |
|
|
374
|
+
| `OrFilter(*filters)` | `T → bool` | Logical OR |
|
|
375
|
+
| `NotFilter(filter)` | `T → bool` | Logical NOT |
|
|
376
|
+
| `ToUpper()` | `str → str` | Uppercase |
|
|
377
|
+
| `ToLower()` | `str → str` | Lowercase |
|
|
378
|
+
| `Contains(s)` | `str → bool` | Substring check |
|
|
379
|
+
| `Map(filter)` | `list → list` | Apply filter to each element |
|
|
380
|
+
| `FilterList(pred)` | `list → list` | Filter elements by predicate |
|
|
381
|
+
| `Compose(*filters)` | `T → U` | Compose multiple filters |
|
|
382
|
+
|
|
383
|
+
### Core Classes
|
|
384
|
+
|
|
385
|
+
| Class | Description |
|
|
386
|
+
|-------|-------------|
|
|
387
|
+
| `Filter[T, U]` | Abstract base class for all filters |
|
|
388
|
+
| `Functional` | Mixin that enables `\|` operator on any class |
|
|
389
|
+
| `FunctionalInt` | Wrapper for `int` with pipe support |
|
|
390
|
+
| `FunctionalFloat` | Wrapper for `float` with pipe support |
|
|
391
|
+
| `FunctionalStr` | Wrapper for `str` with pipe support |
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Examples
|
|
396
|
+
|
|
397
|
+
Run the included examples:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
python -m examples.basics # Introduction to all features
|
|
401
|
+
python -m examples.extending # Creating custom filters
|
|
402
|
+
python -m examples.data_transformation # Real-world patterns
|
|
403
|
+
python -m examples.quick_reference # Quick lookup cheatsheet
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Design Principles
|
|
409
|
+
|
|
410
|
+
1. **Explicit over implicit** — Errors are values, not exceptions
|
|
411
|
+
2. **Composability** — Small, reusable units that combine easily
|
|
412
|
+
3. **Type safety** — Generics provide IDE support and catch bugs early
|
|
413
|
+
4. **Immutability** — Filters return new values, never mutate
|
|
414
|
+
5. **Zero dependencies** — Pure Python, works everywhere
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## License
|
|
419
|
+
|
|
420
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
<p align="center">
|
|
425
|
+
Made with ❤️ for functional programming in Python
|
|
426
|
+
</p>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
punctional/__init__.py,sha256=
|
|
1
|
+
punctional/__init__.py,sha256=0fzsSIiCBnYKBug88xYWHVCWtngDcHIAM_enuqkHmKc,1271
|
|
2
2
|
punctional/arithmetic.py,sha256=TzU5wJqu0olmEyh-TuHIpMH_2mw-EFoDRxmK6kAFbUU,1071
|
|
3
3
|
punctional/comparison.py,sha256=X9SzAXbNXUR5_lmPaZPaTMqM_0zwlvNPb93pcpipogI,850
|
|
4
4
|
punctional/core.py,sha256=g3VuKNpfbIBoNf8OEAUDtc5bLdr-PAbbMwupnkz8wPE,2838
|
|
@@ -7,7 +7,7 @@ punctional/logical.py,sha256=3TR0Bgv-SwUjo4o177hHe8YLTr3jG4Kb13GZBGmEb9w,980
|
|
|
7
7
|
punctional/monads.py,sha256=zDGr7GMhiGc5whNj81lQTDgxfJtBOuh8IuNGpT7gq5g,9739
|
|
8
8
|
punctional/string.py,sha256=E8Ajo7_Rok55_ZNbDFQgCub0VCM4-fW_bUNswyUqDds,607
|
|
9
9
|
punctional/types.py,sha256=gS8Z3XtHKT0dttuGUMFsZWMoiLKuE51cRZMOVVaNKJw,1724
|
|
10
|
-
punctional-0.1.
|
|
11
|
-
punctional-0.1.
|
|
12
|
-
punctional-0.1.
|
|
13
|
-
punctional-0.1.
|
|
10
|
+
punctional-0.1.1.dist-info/METADATA,sha256=LObRFwN3ERvb3uJBYtiinfCbWrR4d6oCq-AJ1A2bnR4,12046
|
|
11
|
+
punctional-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
12
|
+
punctional-0.1.1.dist-info/licenses/LICENSE,sha256=-iDh_gcypGAb576TWRWkMhTMvuAaOML4rZwKV1s8Xoo,1070
|
|
13
|
+
punctional-0.1.1.dist-info/RECORD,,
|
|
@@ -1,445 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: punctional
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: A functional programming framework for Python — composable filters, method chaining, and expressive data pipelines with Option and Result monads.
|
|
5
|
-
Project-URL: Homepage, https://github.com/peghaz/punctional
|
|
6
|
-
Project-URL: Repository, https://github.com/peghaz/punctional
|
|
7
|
-
Project-URL: Documentation, https://github.com/peghaz/punctional#readme
|
|
8
|
-
Project-URL: Issues, https://github.com/peghaz/punctional/issues
|
|
9
|
-
Author-email: Mehdi Peghaz <peghaz@example.com>
|
|
10
|
-
License: MIT
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Keywords: composition,data-pipeline,filters,functional-programming,method-chaining,monads,option,pipe-operator,result
|
|
13
|
-
Classifier: Development Status :: 4 - Beta
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
-
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
-
Classifier: Typing :: Typed
|
|
22
|
-
Requires-Python: >=3.12
|
|
23
|
-
Description-Content-Type: text/markdown
|
|
24
|
-
|
|
25
|
-
# Punctional
|
|
26
|
-
|
|
27
|
-
> A functional programming framework for Python — enabling composable filters, method chaining, and expressive data pipelines.
|
|
28
|
-
|
|
29
|
-
[](https://www.python.org/downloads/)
|
|
30
|
-
[](LICENSE)
|
|
31
|
-
|
|
32
|
-
## 🎯 What is Punctional?
|
|
33
|
-
|
|
34
|
-
**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.
|
|
35
|
-
|
|
36
|
-
Whether you're building data pipelines, validation logic, or just want cleaner, more declarative code — Punctional provides the building blocks.
|
|
37
|
-
|
|
38
|
-
## ✨ Key Features
|
|
39
|
-
|
|
40
|
-
- **🔗 Pipe Operator Chaining** — Chain transformations using the intuitive `|` operator
|
|
41
|
-
- **🧱 Composable Filters** — Create reusable, testable transformation units
|
|
42
|
-
- **📦 Functional Wrappers** — Wrap native types (`int`, `float`, `str`) for functional operations
|
|
43
|
-
- **🎭 Monads** — Built-in `Option` (Some/Nothing) and `Result` (Ok/Error) monads
|
|
44
|
-
- **🔧 Extensible** — Easily create custom filters for your domain
|
|
45
|
-
- **📋 Dataclass Support** — Make any dataclass functional with the `Functional` mixin
|
|
46
|
-
|
|
47
|
-
## 📦 Installation
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
pip install punctional
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Or install from source:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
git clone https://github.com/peghaz/punctional.git
|
|
57
|
-
cd punctional
|
|
58
|
-
pip install -e .
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## 🚀 Quick Start
|
|
62
|
-
|
|
63
|
-
```python
|
|
64
|
-
from punctional import fint, fstr, Add, Mult, ToUpper, GreaterThan, AndFilter, LessThan
|
|
65
|
-
|
|
66
|
-
# Arithmetic chaining
|
|
67
|
-
result = fint(10) | Add(5) | Mult(2) # (10 + 5) * 2 = 30
|
|
68
|
-
|
|
69
|
-
# String transformations
|
|
70
|
-
text = fstr("hello") | ToUpper() | Add("!") # "HELLO!"
|
|
71
|
-
|
|
72
|
-
# Logical validation
|
|
73
|
-
is_valid = fint(42) | AndFilter(GreaterThan(10), LessThan(100)) # True
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## 📚 Core Concepts
|
|
77
|
-
|
|
78
|
-
### Filters
|
|
79
|
-
|
|
80
|
-
A **Filter** is the fundamental building block — a transformation that takes an input and produces an output:
|
|
81
|
-
|
|
82
|
-
```python
|
|
83
|
-
from punctional import Filter, fint
|
|
84
|
-
|
|
85
|
-
class Square(Filter[int, int]):
|
|
86
|
-
def apply(self, value: int) -> int:
|
|
87
|
-
return value ** 2
|
|
88
|
-
|
|
89
|
-
# Use it
|
|
90
|
-
result = fint(5) | Square() # 25
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Functional Wrappers
|
|
94
|
-
|
|
95
|
-
Wrap native Python types to enable pipe operations:
|
|
96
|
-
|
|
97
|
-
| Function | Type | Description |
|
|
98
|
-
|----------|------|-------------|
|
|
99
|
-
| `fint(x)` | `FunctionalInt` | Functional integer wrapper |
|
|
100
|
-
| `ffloat(x)` | `FunctionalFloat` | Functional float wrapper |
|
|
101
|
-
| `fstr(x)` | `FunctionalStr` | Functional string wrapper |
|
|
102
|
-
|
|
103
|
-
### The Pipe Operator
|
|
104
|
-
|
|
105
|
-
Chain filters using the `|` operator for readable, left-to-right transformations:
|
|
106
|
-
|
|
107
|
-
```python
|
|
108
|
-
# Instead of nested calls:
|
|
109
|
-
result = Div(4).apply(Sub(3).apply(Mult(2).apply(Add(5).apply(10))))
|
|
110
|
-
|
|
111
|
-
# Write declaratively:
|
|
112
|
-
result = fint(10) | Add(5) | Mult(2) | Sub(3) | Div(4)
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## 🔧 Built-in Filters
|
|
116
|
-
|
|
117
|
-
### Arithmetic Filters
|
|
118
|
-
|
|
119
|
-
```python
|
|
120
|
-
from punctional import Add, Sub, Mult, Div
|
|
121
|
-
|
|
122
|
-
fint(10) | Add(5) # 15
|
|
123
|
-
fint(10) | Sub(3) # 7
|
|
124
|
-
fint(10) | Mult(2) # 20
|
|
125
|
-
fint(10) | Div(4) # 2.5
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Comparison Filters
|
|
129
|
-
|
|
130
|
-
```python
|
|
131
|
-
from punctional import GreaterThan, LessThan, Equals
|
|
132
|
-
|
|
133
|
-
fint(42) | GreaterThan(10) # True
|
|
134
|
-
fint(5) | LessThan(10) # True
|
|
135
|
-
fint(42) | Equals(42) # True
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Logical Filters
|
|
139
|
-
|
|
140
|
-
```python
|
|
141
|
-
from punctional import AndFilter, OrFilter, NotFilter, GreaterThan, LessThan, Equals
|
|
142
|
-
|
|
143
|
-
# AND: all conditions must be true
|
|
144
|
-
fint(42) | AndFilter(GreaterThan(10), LessThan(100)) # True
|
|
145
|
-
|
|
146
|
-
# OR: at least one condition must be true
|
|
147
|
-
fint(5) | OrFilter(LessThan(10), GreaterThan(100)) # True
|
|
148
|
-
|
|
149
|
-
# NOT: negate a condition
|
|
150
|
-
fint(5) | NotFilter(Equals(0)) # True
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### String Filters
|
|
154
|
-
|
|
155
|
-
```python
|
|
156
|
-
from punctional import ToUpper, ToLower, Contains, fstr
|
|
157
|
-
|
|
158
|
-
fstr("hello") | ToUpper() # "HELLO"
|
|
159
|
-
fstr("WORLD") | ToLower() # "world"
|
|
160
|
-
fstr("hello world") | Contains("world") # True
|
|
161
|
-
fstr("ha") | Mult(3) # "hahaha"
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### List Filters
|
|
165
|
-
|
|
166
|
-
```python
|
|
167
|
-
from punctional import Map, FilterList, Reduce, Mult, GreaterThan
|
|
168
|
-
|
|
169
|
-
numbers = [1, 2, 3, 4, 5]
|
|
170
|
-
|
|
171
|
-
# Transform each element
|
|
172
|
-
Map(Mult(2)).apply(numbers) # [2, 4, 6, 8, 10]
|
|
173
|
-
|
|
174
|
-
# Filter elements
|
|
175
|
-
FilterList(GreaterThan(2)).apply(numbers) # [3, 4, 5]
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Composition
|
|
179
|
-
|
|
180
|
-
Create reusable pipelines with `Compose`:
|
|
181
|
-
|
|
182
|
-
```python
|
|
183
|
-
from punctional import Compose, Mult, Add, fint
|
|
184
|
-
|
|
185
|
-
# Create a reusable transformation
|
|
186
|
-
double_plus_ten = Compose(Mult(2), Add(10))
|
|
187
|
-
|
|
188
|
-
fint(5) | double_plus_ten # 20
|
|
189
|
-
fint(10) | double_plus_ten # 30
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
## 🎭 Functional Design Patterns
|
|
193
|
-
|
|
194
|
-
### Option Monad (Some/Nothing)
|
|
195
|
-
|
|
196
|
-
Handle nullable values without explicit `None` checks:
|
|
197
|
-
|
|
198
|
-
```python
|
|
199
|
-
from punctional import Some, Nothing, some
|
|
200
|
-
|
|
201
|
-
# Wrap a value
|
|
202
|
-
value = Some(42)
|
|
203
|
-
print(value.map(lambda x: x * 2)) # Some(84)
|
|
204
|
-
print(value.get_or_else(0)) # 42
|
|
205
|
-
|
|
206
|
-
# Handle absence
|
|
207
|
-
empty = Nothing()
|
|
208
|
-
print(empty.map(lambda x: x * 2)) # Nothing
|
|
209
|
-
print(empty.get_or_else(0)) # 0
|
|
210
|
-
|
|
211
|
-
# Auto-convert from potentially None values
|
|
212
|
-
result = some(potentially_none_value) # Returns Nothing if None
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
#### Option Operations
|
|
216
|
-
|
|
217
|
-
| Method | Description |
|
|
218
|
-
|--------|-------------|
|
|
219
|
-
| `map(f)` | Transform value if present |
|
|
220
|
-
| `flat_map(f)` | Chain operations returning Option |
|
|
221
|
-
| `bind(f)` | Alias for flat_map |
|
|
222
|
-
| `get_or_else(default)` | Get value or default |
|
|
223
|
-
| `get_or_none()` | Get value or None |
|
|
224
|
-
| `filter(predicate)` | Return Nothing if predicate fails |
|
|
225
|
-
| `or_else(alternative)` | Return alternative if Nothing |
|
|
226
|
-
| `is_some()` | Check if value is present |
|
|
227
|
-
| `is_nothing()` | Check if value is absent |
|
|
228
|
-
|
|
229
|
-
### Result Monad (Ok/Error)
|
|
230
|
-
|
|
231
|
-
Handle operations that can fail with meaningful errors:
|
|
232
|
-
|
|
233
|
-
```python
|
|
234
|
-
from punctional import Ok, Error, try_result
|
|
235
|
-
|
|
236
|
-
# Successful operation
|
|
237
|
-
success = Ok(42)
|
|
238
|
-
print(success.map(lambda x: x * 2)) # Ok(84)
|
|
239
|
-
|
|
240
|
-
# Failed operation
|
|
241
|
-
failure = Error("Something went wrong")
|
|
242
|
-
print(failure.map(lambda x: x * 2)) # Error("Something went wrong")
|
|
243
|
-
|
|
244
|
-
# Wrap potentially throwing functions
|
|
245
|
-
def divide(a, b):
|
|
246
|
-
return a / b
|
|
247
|
-
|
|
248
|
-
result = try_result(lambda: divide(10, 0))
|
|
249
|
-
# Error(ZeroDivisionError(...))
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
#### Result Operations
|
|
253
|
-
|
|
254
|
-
| Method | Description |
|
|
255
|
-
|--------|-------------|
|
|
256
|
-
| `map(f)` | Transform success value |
|
|
257
|
-
| `map_error(f)` | Transform error value |
|
|
258
|
-
| `flat_map(f)` | Chain operations returning Result |
|
|
259
|
-
| `bind(f)` | Alias for flat_map |
|
|
260
|
-
| `get_or_else(default)` | Get value or default |
|
|
261
|
-
| `is_ok()` | Check if successful |
|
|
262
|
-
| `is_error()` | Check if failed |
|
|
263
|
-
| `to_option()` | Convert to Option (discards error info) |
|
|
264
|
-
|
|
265
|
-
## 🏗️ Extending the Framework
|
|
266
|
-
|
|
267
|
-
### Creating Custom Filters
|
|
268
|
-
|
|
269
|
-
#### Simple Filter
|
|
270
|
-
|
|
271
|
-
```python
|
|
272
|
-
from punctional import Filter
|
|
273
|
-
|
|
274
|
-
class Increment(Filter[int, int]):
|
|
275
|
-
def apply(self, value: int) -> int:
|
|
276
|
-
return value + 1
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
#### Parameterized Filter
|
|
280
|
-
|
|
281
|
-
```python
|
|
282
|
-
class Power(Filter[int, int]):
|
|
283
|
-
def __init__(self, exponent: int):
|
|
284
|
-
self.exponent = exponent
|
|
285
|
-
|
|
286
|
-
def apply(self, value: int) -> int:
|
|
287
|
-
return value ** self.exponent
|
|
288
|
-
|
|
289
|
-
fint(2) | Power(3) # 8
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
#### Stateful Filter
|
|
293
|
-
|
|
294
|
-
```python
|
|
295
|
-
class Accumulator(Filter[int, int]):
|
|
296
|
-
def __init__(self, initial: int = 0):
|
|
297
|
-
self.total = initial
|
|
298
|
-
|
|
299
|
-
def apply(self, value: int) -> int:
|
|
300
|
-
self.total += value
|
|
301
|
-
return self.total
|
|
302
|
-
|
|
303
|
-
acc = Accumulator()
|
|
304
|
-
acc(5) # 5
|
|
305
|
-
acc(10) # 15
|
|
306
|
-
acc(3) # 18
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
### Functional Dataclasses
|
|
310
|
-
|
|
311
|
-
Make any dataclass functional with the `Functional` mixin:
|
|
312
|
-
|
|
313
|
-
```python
|
|
314
|
-
from dataclasses import dataclass
|
|
315
|
-
from punctional import Functional, Filter
|
|
316
|
-
|
|
317
|
-
@dataclass
|
|
318
|
-
class Point(Functional):
|
|
319
|
-
x: float
|
|
320
|
-
y: float
|
|
321
|
-
|
|
322
|
-
class ScalePoint(Filter[Point, Point]):
|
|
323
|
-
def __init__(self, factor: float):
|
|
324
|
-
self.factor = factor
|
|
325
|
-
|
|
326
|
-
def apply(self, point: Point) -> Point:
|
|
327
|
-
return Point(point.x * self.factor, point.y * self.factor)
|
|
328
|
-
|
|
329
|
-
# Now Point supports piping!
|
|
330
|
-
point = Point(3, 4)
|
|
331
|
-
scaled = point | ScalePoint(2.5) # Point(7.5, 10.0)
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
## 📖 Examples
|
|
335
|
-
|
|
336
|
-
The [examples/](examples/) directory contains comprehensive examples:
|
|
337
|
-
|
|
338
|
-
| File | Description |
|
|
339
|
-
|------|-------------|
|
|
340
|
-
| [basics.py](examples/basics.py) | Basic usage and introduction to all features |
|
|
341
|
-
| [extending.py](examples/extending.py) | Guide to creating custom filters and domain-specific extensions |
|
|
342
|
-
| [data_transformation.py](examples/data_transformation.py) | Advanced patterns: validation pipelines, data transformations |
|
|
343
|
-
| [quick_reference.py](examples/quick_reference.py) | Cheat sheet for quick lookup |
|
|
344
|
-
|
|
345
|
-
### Run the examples:
|
|
346
|
-
|
|
347
|
-
```bash
|
|
348
|
-
python -m examples.basics
|
|
349
|
-
python -m examples.extending
|
|
350
|
-
python -m examples.data_transformation
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
## 🧪 Common Patterns
|
|
354
|
-
|
|
355
|
-
### Validation Pipeline
|
|
356
|
-
|
|
357
|
-
```python
|
|
358
|
-
from punctional import AndFilter, Functional, Filter
|
|
359
|
-
from dataclasses import dataclass
|
|
360
|
-
|
|
361
|
-
@dataclass
|
|
362
|
-
class Person(Functional):
|
|
363
|
-
name: str
|
|
364
|
-
age: int
|
|
365
|
-
email: str
|
|
366
|
-
|
|
367
|
-
class ValidateName(Filter[Person, bool]):
|
|
368
|
-
def apply(self, person: Person) -> bool:
|
|
369
|
-
return 1 <= len(person.name) <= 100
|
|
370
|
-
|
|
371
|
-
class ValidateAge(Filter[Person, bool]):
|
|
372
|
-
def apply(self, person: Person) -> bool:
|
|
373
|
-
return 0 <= person.age <= 150
|
|
374
|
-
|
|
375
|
-
class ValidateEmail(Filter[Person, bool]):
|
|
376
|
-
def apply(self, person: Person) -> bool:
|
|
377
|
-
return "@" in person.email and "." in person.email
|
|
378
|
-
|
|
379
|
-
# Use the validation pipeline
|
|
380
|
-
person = Person("Alice", 30, "alice@example.com")
|
|
381
|
-
is_valid = person | AndFilter(ValidateName(), ValidateAge(), ValidateEmail()) # True
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
### Data Transformation Pipeline
|
|
385
|
-
|
|
386
|
-
```python
|
|
387
|
-
class ApplyBonus(Filter[Person, Person]):
|
|
388
|
-
def __init__(self, percentage: float):
|
|
389
|
-
self.percentage = percentage
|
|
390
|
-
|
|
391
|
-
def apply(self, person: Person) -> Person:
|
|
392
|
-
return Person(person.name, person.age, person.email)
|
|
393
|
-
|
|
394
|
-
person | ApplyBonus(10) | PromoteAge() | SaveToDatabase()
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
### List Processing Pipeline
|
|
398
|
-
|
|
399
|
-
```python
|
|
400
|
-
from punctional import FilterList, Map, Mult
|
|
401
|
-
|
|
402
|
-
class IsEven(Filter[int, bool]):
|
|
403
|
-
def apply(self, value: int) -> bool:
|
|
404
|
-
return value % 2 == 0
|
|
405
|
-
|
|
406
|
-
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
407
|
-
result = FilterList(IsEven()).apply(numbers) # [2, 4, 6, 8, 10]
|
|
408
|
-
doubled = Map(Mult(2)).apply(result) # [4, 8, 12, 16, 20]
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### Error Handling with Result
|
|
412
|
-
|
|
413
|
-
```python
|
|
414
|
-
from punctional import Result, Ok, Error
|
|
415
|
-
|
|
416
|
-
def fetch_user(id: int) -> Result[dict, str]:
|
|
417
|
-
if id < 0:
|
|
418
|
-
return Error("Invalid ID")
|
|
419
|
-
return Ok({"id": id, "name": "Alice"})
|
|
420
|
-
|
|
421
|
-
result = fetch_user(42).map(lambda u: u["name"]).get_or_else("Unknown") # "Alice"
|
|
422
|
-
result = fetch_user(-1).map(lambda u: u["name"]).get_or_else("Unknown") # "Unknown"
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
## 🎓 Design Principles
|
|
426
|
-
|
|
427
|
-
1. **Immutability** — Filters don't modify input; they return new values
|
|
428
|
-
2. **Composability** — Filters can be combined to create complex transformations
|
|
429
|
-
3. **Type Safety** — Generic types help catch errors at development time
|
|
430
|
-
4. **Readability** — Pipe operator makes data flow explicit and easy to follow
|
|
431
|
-
5. **Extensibility** — Easy to create domain-specific filters
|
|
432
|
-
|
|
433
|
-
## 🤝 Contributing
|
|
434
|
-
|
|
435
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
436
|
-
|
|
437
|
-
## 📄 License
|
|
438
|
-
|
|
439
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
440
|
-
|
|
441
|
-
---
|
|
442
|
-
|
|
443
|
-
<p align="center">
|
|
444
|
-
Made with ❤️ for functional programming enthusiasts
|
|
445
|
-
</p>
|
|
File without changes
|
|
File without changes
|