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 ADDED
@@ -0,0 +1,5 @@
1
+ from .core import ensure, tap, pipe, Chain, Option
2
+
3
+ __all__ = ["ensure", "tap", "pipe", "Chain", "Option"]
4
+
5
+ __version__ = "0.1.0"
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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any