fluently 0.9.0__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.
fluently/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ from fluently.list import fluentlist, flulist, flist
2
+ from fluently.set import fluentset, fluset, fset
3
+ from fluently.tuple import fluenttuple, flutuple, ftuple
4
+
5
+ __all__ = [
6
+ "fluentlist",
7
+ "flulist",
8
+ "flist",
9
+ "fluentset",
10
+ "fluset",
11
+ "fset",
12
+ "fluenttuple",
13
+ "flutuple",
14
+ "ftuple",
15
+ ]
fluently/list.py ADDED
@@ -0,0 +1,346 @@
1
+ from __future__ import annotations
2
+
3
+ from fluently.logging import logger
4
+ from fluently.utilities import filter
5
+ from functools import reduce
6
+
7
+ import random
8
+ import builtins
9
+
10
+ logger = logger.getChild(__name__)
11
+
12
+
13
+ class fluentlist(list):
14
+ """A list subclass with a fluent interface."""
15
+
16
+ def length(self) -> int:
17
+ """Supports returning the count of the total number of items in the list."""
18
+
19
+ return len(self)
20
+
21
+ def clone(self) -> fluentlist[object]:
22
+ """Supports returning a cloned, independent copy of the current list."""
23
+
24
+ return fluentlist(self)
25
+
26
+ def prepend(self, item: object) -> fluentlist[object]:
27
+ """Supports prepending the specified item to the start of the list."""
28
+
29
+ super().insert(0, item)
30
+
31
+ return self
32
+
33
+ def append(self, item: object) -> fluentlist[object]:
34
+ """Supports appending the specified item to the end of the list."""
35
+
36
+ super().append(item)
37
+
38
+ return self
39
+
40
+ def extend(self, iterable) -> fluentlist[object]:
41
+ """Supports extending the current list with the specified items by appending."""
42
+
43
+ super().extend(iterable)
44
+
45
+ return self
46
+
47
+ def insert(self, index: int, item: object) -> fluentlist[object]:
48
+ """Supports inserting the specified item into the list at the specified index."""
49
+
50
+ super().insert(index, item)
51
+
52
+ return self
53
+
54
+ def remove(self, item: object, raises: bool = True) -> fluentlist[object]:
55
+ """Supports removing the first occurance of the specified item from the list."""
56
+
57
+ if not isinstance(raises, bool):
58
+ raise TypeError("The 'raises' argument must have a boolean value!")
59
+
60
+ try:
61
+ super().remove(item)
62
+ except ValueError as exception:
63
+ if raises is True:
64
+ raise exception
65
+ else:
66
+ logger.error(str(exception))
67
+
68
+ return self
69
+
70
+ def removeall(self, item: object, raises: bool = True) -> fluentlist[object]:
71
+ """Supports removing all occurances of the specified item from the list."""
72
+
73
+ if not isinstance(raises, bool):
74
+ raise TypeError("The 'raises' argument must have a boolean value!")
75
+
76
+ while item in self:
77
+ try:
78
+ super().remove(item)
79
+ except ValueError as exception:
80
+ if raises is True:
81
+ raise exception
82
+ else:
83
+ logger.error(str(exception))
84
+
85
+ return self
86
+
87
+ def discard(self, item: object) -> fluentlist[object]:
88
+ """Supports removing the specified item from the list, without raising an error
89
+ should the item be found not to exist - consistent with behaviour of sets."""
90
+
91
+ try:
92
+ super().remove(item)
93
+ except ValueError:
94
+ pass
95
+
96
+ return self
97
+
98
+ def clear(self) -> fluentlist[object]:
99
+ """Supports removing all of the items from the list."""
100
+
101
+ super().clear()
102
+
103
+ return self
104
+
105
+ def repeat(self, count: int) -> fluentlist[object]:
106
+ """Supports repeating the contents of the list the specified number of times."""
107
+
108
+ if not isinstance(count, int):
109
+ raise TypeError("The 'count' argument must have an integer value!")
110
+ elif not count >= 1:
111
+ raise ValueError(
112
+ "The 'count' argument must have an integer value of 1 or more!"
113
+ )
114
+
115
+ for rep in range(0, count - 1):
116
+ self.extend(self)
117
+
118
+ return self
119
+
120
+ def reverse(self) -> fluentlist[object]:
121
+ """Supports reversing the order of the items in the list."""
122
+
123
+ super().reverse()
124
+
125
+ return self
126
+
127
+ def shuffle(self) -> fluentlist[object]:
128
+ """Supports randomly suffling the order of the items in the list."""
129
+
130
+ random.shuffle(self)
131
+
132
+ return self
133
+
134
+ def slice(self, start: int, stop: int = None, step: int = 1) -> fluentlist[object]:
135
+ """Supports returning a new list containing the sliced part of the list."""
136
+
137
+ if not isinstance(start, int):
138
+ raise TypeError("The 'start' argument must have an integer value!")
139
+
140
+ if stop is None:
141
+ pass
142
+ elif not isinstance(stop, int):
143
+ raise TypeError("The 'stop' argument must have an integer value!")
144
+
145
+ if not isinstance(step, int):
146
+ raise TypeError("The 'step' argument must have an integer value!")
147
+
148
+ return fluentlist(self[builtins.slice(start, stop, step)])
149
+
150
+ def take(self, index: int) -> fluentlist[object]:
151
+ """Supports returning a new list containing the items from the start of the
152
+ list until the index specified; the original list remains unmodified."""
153
+
154
+ if not isinstance(index, int):
155
+ raise TypeError("The 'index' argument must have an integer value!")
156
+
157
+ return self.slice(start=0, stop=index)
158
+
159
+ def drop(self, index: int) -> fluentlist[object]:
160
+ """Supports returning a new list containing the items from the specified
161
+ index until the end of the list; the original list remains unmodified."""
162
+
163
+ if not isinstance(index, int):
164
+ raise TypeError("The 'index' argument must have an integer value!")
165
+
166
+ return self.slice(start=index)
167
+
168
+ def swap(self, source: int, target: int) -> fluentlist[object]:
169
+ """Supports swapping the list items at the source and target indices."""
170
+
171
+ length: int = len(self)
172
+
173
+ if not isinstance(source, int):
174
+ raise TypeError("The 'source' index must have an integer value!")
175
+ elif not source < length:
176
+ raise ValueError(
177
+ "The 'source' index must have an integer value smaller than the length of the list!"
178
+ )
179
+ elif source < 0 and (0 - source) >= length:
180
+ raise ValueError(
181
+ "The 'source' index must have an integer value smaller than the length of the list!"
182
+ )
183
+
184
+ if not isinstance(target, int):
185
+ raise TypeError("The 'target' index must have an integer value!")
186
+ elif not target < length:
187
+ raise ValueError(
188
+ "The 'target' index must have an integer value smaller than the length of the list!"
189
+ )
190
+ elif target < 0 and (0 - target) >= length:
191
+ raise ValueError(
192
+ "The 'target' index must have an integer value smaller than the length of the list!"
193
+ )
194
+
195
+ source_value = self[source]
196
+ target_value = self[target]
197
+
198
+ self[target] = source_value
199
+ self[source] = target_value
200
+
201
+ return self
202
+
203
+ def unique(self) -> fluentlist[object]:
204
+ """Supports returning a new version of the list without duplicate values."""
205
+
206
+ seenit: set = set()
207
+ unique: list = []
208
+
209
+ for item in self:
210
+ if item not in seenit:
211
+ seenit.add(item)
212
+ unique.append(item)
213
+
214
+ return fluentlist(unique)
215
+
216
+ def count(self, value: object) -> int:
217
+ """Supports returning a count of how many list items have the specified value."""
218
+
219
+ found: int = 0
220
+
221
+ for item in self:
222
+ if item == value:
223
+ found += 1
224
+
225
+ return found
226
+
227
+ def contains(self, value: object) -> bool:
228
+ """Supports returning if the list contains the specified value or not."""
229
+
230
+ return value in self
231
+
232
+ def any(self, value: object) -> bool:
233
+ """Supports returning if the list contains the specified value at least once."""
234
+
235
+ return self.count(value) >= 1
236
+
237
+ def all(self, value: object) -> bool:
238
+ """Supports returning if the list is completely filled with the specified value."""
239
+
240
+ return self.count(value) == self.length()
241
+
242
+ def map(self, function: callable) -> fluentlist[object]:
243
+ """Supports running a callback on each item in the list returning a new list."""
244
+
245
+ if not callable(function):
246
+ raise TypeError("The 'function' argument must reference a callable!")
247
+
248
+ return fluentlist(builtins.map(function, self))
249
+
250
+ def reduce(self, function: callable, initialiser=None) -> object:
251
+ """Supports running a callback on each item in the list returning the reduced value."""
252
+
253
+ if not callable(function):
254
+ raise TypeError("The 'function' argument must reference a callable!")
255
+
256
+ items = self if (initialiser is None) else self.clone().prepend(initialiser)
257
+
258
+ return reduce(function, items)
259
+
260
+ def sort(self, *args, **kwargs) -> fluentlist[object]:
261
+ """Provides a fluent interface for sorting the current list in-place."""
262
+
263
+ super().sort(*args, **kwargs)
264
+
265
+ return self
266
+
267
+ def sorted(self, *args, **kwargs) -> fluentlist[object]:
268
+ """The sorted method provides a fluent interface for sorting the current list,
269
+ returning a new list with the items ordered according to the specified sort."""
270
+
271
+ return fluentlist(builtins.sorted(self, *args, **kwargs))
272
+
273
+ def filter(
274
+ self, predicate: callable = None, **filters: dict[str, object]
275
+ ) -> fluentlist[object]:
276
+ """Provides a fluent interface for filtering the current list."""
277
+
278
+ if predicate is None:
279
+ pass
280
+ elif not callable(predicate):
281
+ raise TypeError(
282
+ "The 'predicate' argument, if specified, must reference a callable!"
283
+ )
284
+
285
+ if predicate:
286
+ return fluentlist(builtins.filter(predicate, self))
287
+ else:
288
+ return fluentlist(filter(self, **filters))
289
+
290
+ def first(
291
+ self, predicate: callable = None, **filters: dict[str, object]
292
+ ) -> object | None:
293
+ """Supports returning the first element or None if the list is empty."""
294
+
295
+ if len(filters) > 0:
296
+ items = self.filter(predicate=predicate, **filters)
297
+ else:
298
+ items = self
299
+
300
+ return items[0] if (len(items) >= 1) else None
301
+
302
+ def last(
303
+ self, predicate: callable = None, **filters: dict[str, object]
304
+ ) -> object | None:
305
+ """Supports returning the last element or None if the list is empty."""
306
+
307
+ if len(filters) > 0:
308
+ items = self.filter(predicate=predicate, **filters)
309
+ else:
310
+ items = self
311
+
312
+ return items[-1] if (len(items) >= 1) else None
313
+
314
+ def __add__(self, items: list[object]) -> fluentlist[object]:
315
+ """Supports appending items to a clone of the list via the '+' syntax."""
316
+
317
+ return self.clone().extend(items)
318
+
319
+ def __iadd__(self, items: list[object]) -> fluentlist[object]:
320
+ """Supports appending items to the current list in-place via the '+=' syntax."""
321
+
322
+ return self.extend(items)
323
+
324
+ def __mul__(self, count: int) -> fluentlist[object]:
325
+ """Supports appending items to a clone of the list via the '*' syntax."""
326
+
327
+ return self.clone().repeat(count)
328
+
329
+ def __imul__(self, count: int) -> fluentlist[object]:
330
+ """Supports multiplying the current list in-place via the '*=' syntax."""
331
+
332
+ return self.repeat(count)
333
+
334
+ def __sub__(self, item: object) -> fluentlist[object]:
335
+ """Supports removing specified item from a clone of the list via the '-' syntax."""
336
+
337
+ return self.clone().remove(item)
338
+
339
+ def __isub__(self, item: object) -> fluentlist[object]:
340
+ """Supports removing the specified item from the current list in-place via the '-=' syntax."""
341
+
342
+ return self.remove(item)
343
+
344
+
345
+ # Shorthand aliases
346
+ flist = flulist = fluentlist
fluently/logging.py ADDED
@@ -0,0 +1,3 @@
1
+ import logging
2
+
3
+ logger = logging.getLogger("fluently")
fluently/set.py ADDED
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ from fluently.logging import logger
4
+
5
+ logger = logger.getChild(__name__)
6
+
7
+
8
+ class fluentset(set):
9
+ """A set subclass with a fluent interface."""
10
+
11
+ def length(self) -> int:
12
+ """Supports returning the count of the total number of items in the set."""
13
+
14
+ return len(self)
15
+
16
+ def clone(self) -> fluentset[object]:
17
+ """Supports returning a cloned, independent copy of the current set."""
18
+
19
+ return fluentset(self)
20
+
21
+ def add(self, item: object) -> fluentset[object]:
22
+ """Supports adding the specified item to the current set if not already present."""
23
+
24
+ super().add(item)
25
+
26
+ return self
27
+
28
+ def remove(self, item: object, raises: bool = True) -> fluentset[object]:
29
+ """Supports removing the specified item from the current set if present; if the
30
+ item is not present, and the `raises` keyword argument is set to its default of
31
+ `True` then the method will raise a `KeyError` exception noting the absence
32
+ of the specified item; if `raises` is set to `False`, the method will not raise
33
+ and exception but will log the absence of the item via the standard logger."""
34
+
35
+ try:
36
+ super().remove(item)
37
+ except KeyError as exception:
38
+ if raises is True:
39
+ raise exception
40
+ else:
41
+ logger.error(str(exception))
42
+
43
+ return self
44
+
45
+ def discard(self, item: object) -> fluentset[object]:
46
+ """Supports removing the specified item from the set."""
47
+
48
+ super().discard(item)
49
+
50
+ return self
51
+
52
+ def clear(self) -> fluentset[object]:
53
+ """Supports removing all of the items from the set."""
54
+
55
+ super().clear()
56
+
57
+ return self
58
+
59
+ def contains(self, value: object) -> bool:
60
+ """Supports returning if the set contains the specified value or not."""
61
+
62
+ return value in self
63
+
64
+
65
+ # Shorthand aliases
66
+ fset = fluset = fluentset