purely 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.
- purely/__init__.py +5 -0
- purely/core.py +175 -0
- purely-0.1.1.dist-info/METADATA +11 -0
- purely-0.1.1.dist-info/RECORD +5 -0
- purely-0.1.1.dist-info/WHEEL +4 -0
purely/__init__.py
ADDED
purely/core.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from typing import Callable, Any
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
PURELY 💧
|
|
6
|
+
A lightweight library for cleaner, safer, and more fluent Python.
|
|
7
|
+
Embrace purity, banish boilerplate.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Sentinel for missing values
|
|
11
|
+
_SENTINEL = object()
|
|
12
|
+
|
|
13
|
+
# -----------------------------------------------------------------------------
|
|
14
|
+
# 1. THE ESSENTIALS (Direct helpers)
|
|
15
|
+
# -----------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def ensure[T](
|
|
19
|
+
value: T | None, error: str | Exception = ValueError("Value is None")
|
|
20
|
+
) -> T:
|
|
21
|
+
"""
|
|
22
|
+
Takes a value T | None. Returns T or raises an exception.
|
|
23
|
+
Similar to Rust's unwrap().
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
user_id = purely.ensure(get_id(), "User ID missing")
|
|
27
|
+
"""
|
|
28
|
+
if value is None:
|
|
29
|
+
if isinstance(error, str):
|
|
30
|
+
raise ValueError(error)
|
|
31
|
+
raise error
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def tap[T](value: T, func: Callable[[T], Any]) -> T:
|
|
36
|
+
"""
|
|
37
|
+
Executes a function for side effects (like logging) and returns the original value.
|
|
38
|
+
Useful for debugging fluent chains without breaking them.
|
|
39
|
+
"""
|
|
40
|
+
func(value)
|
|
41
|
+
return value
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def pipe[T](value: T, *funcs: Callable[[Any], Any]) -> Any:
|
|
45
|
+
"""
|
|
46
|
+
Pipes a value through a sequence of functions.
|
|
47
|
+
pipe(x, f, g, h) is equivalent to h(g(f(x)))
|
|
48
|
+
"""
|
|
49
|
+
result = value
|
|
50
|
+
for func in funcs:
|
|
51
|
+
result = func(result)
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# -----------------------------------------------------------------------------
|
|
56
|
+
# 2. THE FLUENT INTERFACE (Chain)
|
|
57
|
+
# -----------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Chain[T]:
|
|
61
|
+
"""
|
|
62
|
+
A wrapper to allow method chaining on any object.
|
|
63
|
+
|
|
64
|
+
Usage:
|
|
65
|
+
Chain(5).map(double).value
|
|
66
|
+
Chain(5) | double | str
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(self, value: T):
|
|
70
|
+
self._value = value
|
|
71
|
+
|
|
72
|
+
def map[U](self, func: Callable[[T], U]) -> "Chain[U]":
|
|
73
|
+
"""Apply a function to the contained value."""
|
|
74
|
+
return Chain(func(self._value))
|
|
75
|
+
|
|
76
|
+
def then[U](self, func: Callable[[T], U]) -> "Chain[U]":
|
|
77
|
+
"""Alias for map, reads better in some contexts."""
|
|
78
|
+
return self.map(func)
|
|
79
|
+
|
|
80
|
+
def __or__[U](self, func: Callable[[T], U]) -> "Chain[U]":
|
|
81
|
+
"""
|
|
82
|
+
Allows usage of the pipe operator | to chain functions.
|
|
83
|
+
Chain(5) | double | str
|
|
84
|
+
"""
|
|
85
|
+
return self.map(func)
|
|
86
|
+
|
|
87
|
+
def tap(self, func: Callable[[T], Any]) -> "Chain[T]":
|
|
88
|
+
"""Run a side effect, ignore return, pass original value forward."""
|
|
89
|
+
func(self._value)
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
def ensure(
|
|
93
|
+
self, error: str | Exception = ValueError("Chain value is None")
|
|
94
|
+
) -> "Chain[T]":
|
|
95
|
+
"""Asserts the internal value is not None."""
|
|
96
|
+
ensure(self._value, error)
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
def __eq__(self, other: object) -> bool:
|
|
100
|
+
"""Checks equality against another Chain or the raw value."""
|
|
101
|
+
if isinstance(other, Chain):
|
|
102
|
+
return self._value == other._value
|
|
103
|
+
|
|
104
|
+
return self._value == other
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def value(self) -> T:
|
|
108
|
+
"""Return the raw value (property). No checks performed."""
|
|
109
|
+
return self._value
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# -----------------------------------------------------------------------------
|
|
113
|
+
# 3. RUST-STYLE OPTION (Safe handling)
|
|
114
|
+
# -----------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class Option[T]:
|
|
118
|
+
"""
|
|
119
|
+
A container that represents either a value (Some) or nothing (None).
|
|
120
|
+
Forces safe handling of nullables.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self, value: T | None):
|
|
124
|
+
self._value = value
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def of(cls, value: T | None) -> "Option[T]":
|
|
128
|
+
return cls(value)
|
|
129
|
+
|
|
130
|
+
def is_some(self) -> bool:
|
|
131
|
+
return self._value is not None
|
|
132
|
+
|
|
133
|
+
def is_none(self) -> bool:
|
|
134
|
+
return self._value is None
|
|
135
|
+
|
|
136
|
+
def unwrap(
|
|
137
|
+
self,
|
|
138
|
+
default: Any = _SENTINEL,
|
|
139
|
+
error: str | Exception = ValueError("Called unwrap on None"),
|
|
140
|
+
) -> T:
|
|
141
|
+
"""
|
|
142
|
+
Returns the contained value.
|
|
143
|
+
|
|
144
|
+
If the value is None:
|
|
145
|
+
1. Returns 'default' if provided.
|
|
146
|
+
2. Raises 'error' if no default is provided.
|
|
147
|
+
"""
|
|
148
|
+
if self._value is not None:
|
|
149
|
+
return self._value
|
|
150
|
+
|
|
151
|
+
if default is not _SENTINEL:
|
|
152
|
+
return default
|
|
153
|
+
|
|
154
|
+
return ensure(self._value, error)
|
|
155
|
+
|
|
156
|
+
def map[U](self, func: Callable[[T], U]) -> "Option[U]":
|
|
157
|
+
"""If Some, applies func. If None, stays None."""
|
|
158
|
+
if self._value is None:
|
|
159
|
+
return Option(None)
|
|
160
|
+
|
|
161
|
+
return Option(func(self._value))
|
|
162
|
+
|
|
163
|
+
def filter(self, predicate: Callable[[T], bool]) -> "Option[T]":
|
|
164
|
+
"""If Some and predicate is true, keep it. Else become None."""
|
|
165
|
+
if self._value is not None and predicate(self._value):
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
return Option(None)
|
|
169
|
+
|
|
170
|
+
def __eq__(self, other: object) -> bool:
|
|
171
|
+
"""Checks equality against another Option or the raw value."""
|
|
172
|
+
if isinstance(other, Option):
|
|
173
|
+
return self._value == other._value
|
|
174
|
+
|
|
175
|
+
return self._value == other
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: purely
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Unobstrusive functional programming in Python.
|
|
5
|
+
Author-email: Alejandro Piad <apiad@apiad.net>
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# purely
|
|
10
|
+
|
|
11
|
+
A minimalistic library for functional programming in Python.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
purely/__init__.py,sha256=2CnCcaY3idaeW8W4Ae90ra9KrVoBe1_bxyFU9FYPaJs,130
|
|
2
|
+
purely/core.py,sha256=AWrZVKqp0jdE2ug4uFiJR3gAZx_4QE5aN1pNkO-3hGk,4970
|
|
3
|
+
purely-0.1.1.dist-info/METADATA,sha256=WAYaHO9gTrsBBl4TrzOmeXxziclBVgnCJZZ_1evf6xM,289
|
|
4
|
+
purely-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
5
|
+
purely-0.1.1.dist-info/RECORD,,
|