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