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/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