flpit 0.2.2__tar.gz → 0.2.3__tar.gz
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.
- {flpit-0.2.2 → flpit-0.2.3}/PKG-INFO +1 -1
- {flpit-0.2.2 → flpit-0.2.3}/pyproject.toml +2 -2
- {flpit-0.2.2 → flpit-0.2.3}/src/flpit/core/linq.py +46 -39
- {flpit-0.2.2 → flpit-0.2.3}/.gitignore +0 -0
- {flpit-0.2.2 → flpit-0.2.3}/LICENSE +0 -0
- {flpit-0.2.2 → flpit-0.2.3}/README.md +0 -0
- {flpit-0.2.2 → flpit-0.2.3}/src/flp/__init__.py +0 -0
- {flpit-0.2.2 → flpit-0.2.3}/src/flpit/__init__.py +0 -0
- {flpit-0.2.2 → flpit-0.2.3}/src/flpit/core/__init__.py +0 -0
- {flpit-0.2.2 → flpit-0.2.3}/src/flpit/csv.py +0 -0
- {flpit-0.2.2 → flpit-0.2.3}/src/flpit/flp.py +0 -0
- {flpit-0.2.2 → flpit-0.2.3}/src/flpit/json.py +0 -0
- {flpit-0.2.2 → flpit-0.2.3}/src/flpit/py.typed +0 -0
- {flpit-0.2.2 → flpit-0.2.3}/src/flpit/task.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "flpit"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3"
|
|
4
4
|
description = "Fluent LINQ for Python — flip your iterables"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -55,7 +55,7 @@ include = ["src/flp", "src/flpit"]
|
|
|
55
55
|
[tool.pyrefly]
|
|
56
56
|
project-includes = ["src"]
|
|
57
57
|
project-excludes = ["**/__pycache__", "tests"]
|
|
58
|
-
search-path = ["src"]
|
|
58
|
+
search-path = ["src", "typing"]
|
|
59
59
|
preset = "strict"
|
|
60
60
|
|
|
61
61
|
[tool.pytest.ini_options]
|
|
@@ -14,7 +14,6 @@ from itertools import islice
|
|
|
14
14
|
from typing import (
|
|
15
15
|
Any,
|
|
16
16
|
Callable,
|
|
17
|
-
Generic,
|
|
18
17
|
Iterable,
|
|
19
18
|
Iterator,
|
|
20
19
|
Collection,
|
|
@@ -27,14 +26,14 @@ from typing import (
|
|
|
27
26
|
Union,
|
|
28
27
|
override,
|
|
29
28
|
overload,
|
|
30
|
-
cast as typing_cast,
|
|
29
|
+
cast as typing_cast,
|
|
30
|
+
BinaryIO,
|
|
31
|
+
Concatenate,
|
|
32
|
+
Sized
|
|
31
33
|
)
|
|
32
34
|
|
|
33
|
-
import flpit.json
|
|
34
35
|
from flpit.json import JsonConvert, JsonSerializerOptions
|
|
35
36
|
|
|
36
|
-
TItem = TypeVar("TItem")
|
|
37
|
-
TResult = TypeVar("TResult")
|
|
38
37
|
TKey = TypeVar("TKey")
|
|
39
38
|
TOther = TypeVar("TOther")
|
|
40
39
|
TAccumulate = TypeVar("TAccumulate")
|
|
@@ -114,16 +113,24 @@ class ArgumentOutOfBoundsError(ArgumentError):
|
|
|
114
113
|
super().__init__("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.")
|
|
115
114
|
|
|
116
115
|
|
|
117
|
-
def _guard_empty
|
|
118
|
-
|
|
116
|
+
def _guard_empty[SelfT: Sized, R, **P](
|
|
117
|
+
func: Callable[Concatenate[SelfT, P], R],
|
|
118
|
+
) -> Callable[Concatenate[SelfT, P], R]:
|
|
119
|
+
"""Translate empty-sequence ValueError into EmptySequenceError."""
|
|
120
|
+
|
|
119
121
|
@wraps(func)
|
|
120
|
-
def wrapper(
|
|
122
|
+
def wrapper(
|
|
123
|
+
self: SelfT,
|
|
124
|
+
*args: P.args,
|
|
125
|
+
**kwargs: P.kwargs,
|
|
126
|
+
) -> R:
|
|
121
127
|
try:
|
|
122
128
|
return func(self, *args, **kwargs)
|
|
123
129
|
except ValueError as e:
|
|
124
|
-
if
|
|
130
|
+
if len(self) == 0:
|
|
125
131
|
raise EmptySequenceError() from e
|
|
126
132
|
raise
|
|
133
|
+
|
|
127
134
|
return wrapper
|
|
128
135
|
|
|
129
136
|
|
|
@@ -137,7 +144,7 @@ def dotnet_context() -> AbstractContextManager[decimal.Context]:
|
|
|
137
144
|
return decimal.localcontext(ctx)
|
|
138
145
|
|
|
139
146
|
|
|
140
|
-
class _FactoryIterable
|
|
147
|
+
class _FactoryIterable[TItem](Iterable[TItem]):
|
|
141
148
|
"""
|
|
142
149
|
Wraps a generator factory function into a clean Iterable[T].
|
|
143
150
|
Ensures __iter__ produces a brand-new iterator instance on every call
|
|
@@ -152,7 +159,7 @@ class _FactoryIterable(Iterable[TItem], Generic[TItem]):
|
|
|
152
159
|
return self._factory()
|
|
153
160
|
|
|
154
161
|
|
|
155
|
-
class FlpIt
|
|
162
|
+
class FlpIt[TItem](Iterable[TItem]):
|
|
156
163
|
"""
|
|
157
164
|
| Fluent Iterable
|
|
158
165
|
Lazy evaluation wrapper around an iterable, inspired by .NET LINQ's
|
|
@@ -173,7 +180,7 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
|
|
|
173
180
|
return iter(self._iterable)
|
|
174
181
|
|
|
175
182
|
# noinspection unused-parameter
|
|
176
|
-
def as_type(self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
183
|
+
def as_type[TResult](self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
177
184
|
"""Zero-cost static type hint update returning self directly without checks or allocations."""
|
|
178
185
|
return self # type: ignore[return-value]
|
|
179
186
|
|
|
@@ -257,7 +264,7 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
|
|
|
257
264
|
# return typing_cast(FlpIt[TItem], FlpIt[TItem](_FactoryIterable(_generator)))
|
|
258
265
|
return FlpIt[TItem](_FactoryIterable(_generator))
|
|
259
266
|
|
|
260
|
-
def select(self, selector: Callable[[TItem], TResult]) -> FlpIt[TResult]:
|
|
267
|
+
def select[TResult](self, selector: Callable[[TItem], TResult]) -> FlpIt[TResult]:
|
|
261
268
|
"""Projects each element into a new form."""
|
|
262
269
|
def _generator() -> Iterator[TResult]:
|
|
263
270
|
for item in self:
|
|
@@ -265,7 +272,7 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
|
|
|
265
272
|
|
|
266
273
|
return FlpIt(_FactoryIterable(_generator))
|
|
267
274
|
|
|
268
|
-
def select_many(
|
|
275
|
+
def select_many[TResult](
|
|
269
276
|
self, selector: Callable[[TItem], Iterable[TResult]]
|
|
270
277
|
) -> FlpIt[TResult]:
|
|
271
278
|
"""Flattens sequence projections."""
|
|
@@ -310,7 +317,7 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
|
|
|
310
317
|
|
|
311
318
|
return FlpIt(_FactoryIterable(_generator))
|
|
312
319
|
|
|
313
|
-
def cast(self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
320
|
+
def cast[TResult](self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
314
321
|
"""Casts elements to a specified type or raises TypeError if cast fails."""
|
|
315
322
|
def _generator() -> Iterator[TResult]:
|
|
316
323
|
for item in self:
|
|
@@ -322,7 +329,7 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
|
|
|
322
329
|
|
|
323
330
|
return FlpIt(_FactoryIterable(_generator))
|
|
324
331
|
|
|
325
|
-
def of_type(self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
332
|
+
def of_type[TResult](self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
326
333
|
"""Filters the elements of an Iterable based on a specified type."""
|
|
327
334
|
def _generator() -> Iterator[TResult]:
|
|
328
335
|
for item in self:
|
|
@@ -360,7 +367,7 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
|
|
|
360
367
|
def zip(self, second: Iterable[TOther]) -> FlpIt[tuple[TItem, TOther]]: ...
|
|
361
368
|
|
|
362
369
|
@overload
|
|
363
|
-
def zip(
|
|
370
|
+
def zip[TResult, TOther](
|
|
364
371
|
self,
|
|
365
372
|
second: Iterable[TOther],
|
|
366
373
|
result_selector: Callable[[TItem, TOther], TResult],
|
|
@@ -598,7 +605,7 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
|
|
|
598
605
|
return item
|
|
599
606
|
raise EmptySequenceError() if predicate is _SENTINEL else NoMatchError()
|
|
600
607
|
|
|
601
|
-
def first_or_default(
|
|
608
|
+
def first_or_default[TResult](
|
|
602
609
|
self, default: TResult, predicate: Callable[[TItem], bool] | _Sentinel = _SENTINEL
|
|
603
610
|
) -> Union[TItem, TResult]:
|
|
604
611
|
"""Returns the first element matching a predicate, or a default value."""
|
|
@@ -673,14 +680,7 @@ class _NoneOrderKey:
|
|
|
673
680
|
|
|
674
681
|
_NONE_ORDER_KEY = _NoneOrderKey()
|
|
675
682
|
|
|
676
|
-
|
|
677
|
-
selector: Callable[[TItem], Any],
|
|
678
|
-
item: TItem,
|
|
679
|
-
) -> Any:
|
|
680
|
-
key = selector(item)
|
|
681
|
-
return _NONE_ORDER_KEY if key is None else key
|
|
682
|
-
|
|
683
|
-
class OrderedIt(FlpIt[TItem]):
|
|
683
|
+
class OrderedIt[TItem](FlpIt[TItem]):
|
|
684
684
|
__slots__ = ("_criteria",)
|
|
685
685
|
|
|
686
686
|
def __init__(
|
|
@@ -701,13 +701,21 @@ class OrderedIt(FlpIt[TItem]):
|
|
|
701
701
|
else:
|
|
702
702
|
self._criteria = _criteria + ((selector, descending),)
|
|
703
703
|
|
|
704
|
+
@staticmethod
|
|
705
|
+
def __none_aware_key(
|
|
706
|
+
selector: Callable[[TItem], Any],
|
|
707
|
+
item: TItem,
|
|
708
|
+
) -> Any:
|
|
709
|
+
key = selector(item)
|
|
710
|
+
return _NONE_ORDER_KEY if key is None else key
|
|
711
|
+
|
|
704
712
|
@override
|
|
705
713
|
def __iter__(self) -> Iterator[TItem]:
|
|
706
714
|
items = list(self._iterable)
|
|
707
715
|
|
|
708
716
|
for selector, descending in reversed(self._criteria):
|
|
709
717
|
items.sort(
|
|
710
|
-
key=partial(
|
|
718
|
+
key=partial(self.__none_aware_key, selector),
|
|
711
719
|
reverse=descending,
|
|
712
720
|
)
|
|
713
721
|
|
|
@@ -736,7 +744,7 @@ class OrderedIt(FlpIt[TItem]):
|
|
|
736
744
|
)
|
|
737
745
|
|
|
738
746
|
|
|
739
|
-
class Grouping
|
|
747
|
+
class Grouping[TKey, TItem](FlpIt[TItem]):
|
|
740
748
|
"""Represents a collection of elements sharing a common key (.NET IGrouping<TKey, TElement>)."""
|
|
741
749
|
__slots__ = ("_key", )
|
|
742
750
|
|
|
@@ -760,7 +768,7 @@ class Grouping(FlpIt[TItem], Generic[TKey, TItem]):
|
|
|
760
768
|
return self.key == other.key and self.to_list() == other.to_list()
|
|
761
769
|
|
|
762
770
|
|
|
763
|
-
class FlpList
|
|
771
|
+
class FlpList[TItem](Collection[TItem]):
|
|
764
772
|
"""
|
|
765
773
|
| Fluent List
|
|
766
774
|
A materialized list extending UserList that yields lazy FlpIt instances for query operations.
|
|
@@ -781,7 +789,7 @@ class FlpList(Collection[TItem], Generic[TItem]):
|
|
|
781
789
|
# =====================================================================
|
|
782
790
|
# 1. THE ABC CONTRACT
|
|
783
791
|
# =====================================================================
|
|
784
|
-
def __iter__(self) -> Iterator[
|
|
792
|
+
def __iter__(self) -> Iterator[TItem]:
|
|
785
793
|
return self.__list.__iter__()
|
|
786
794
|
|
|
787
795
|
def __len__(self) -> int:
|
|
@@ -1032,17 +1040,16 @@ class FlpList(Collection[TItem], Generic[TItem]):
|
|
|
1032
1040
|
def concat(self, second: Iterable[TItem]) -> FlpIt[TItem]:
|
|
1033
1041
|
return FlpIt(self.__list.data).concat(second)
|
|
1034
1042
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
return self # type: ignore[return-value]
|
|
1043
|
+
def as_type[TResult](self, target_type: Type[TResult]) -> FlpList[TResult]:
|
|
1044
|
+
return typing_cast(FlpList[TResult], self)
|
|
1038
1045
|
|
|
1039
1046
|
def where(self, predicate: Callable[[TItem], bool]) -> FlpIt[TItem]:
|
|
1040
1047
|
return FlpIt[TItem](self.__list.data).where(predicate)
|
|
1041
1048
|
|
|
1042
|
-
def select(self, selector: Callable[[TItem], TResult]) -> FlpIt[TResult]:
|
|
1049
|
+
def select[TResult](self, selector: Callable[[TItem], TResult]) -> FlpIt[TResult]:
|
|
1043
1050
|
return FlpIt(self.__list.data).select(selector)
|
|
1044
1051
|
|
|
1045
|
-
def select_many(
|
|
1052
|
+
def select_many[TResult](
|
|
1046
1053
|
self, selector: Callable[[TItem], Iterable[TResult]]
|
|
1047
1054
|
) -> FlpIt[TResult]:
|
|
1048
1055
|
return FlpIt(self.__list.data).select_many(selector)
|
|
@@ -1053,10 +1060,10 @@ class FlpList(Collection[TItem], Generic[TItem]):
|
|
|
1053
1060
|
def take(self, count: int) -> FlpIt[TItem]:
|
|
1054
1061
|
return FlpIt(self.__list.data).take(count)
|
|
1055
1062
|
|
|
1056
|
-
def cast(self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
1063
|
+
def cast[TResult](self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
1057
1064
|
return FlpIt(self.__list.data).cast(target_type)
|
|
1058
1065
|
|
|
1059
|
-
def of_type(self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
1066
|
+
def of_type[TResult](self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
1060
1067
|
return FlpIt(self.__list.data).of_type(target_type)
|
|
1061
1068
|
|
|
1062
1069
|
def distinct(self) -> FlpIt[TItem]:
|
|
@@ -1071,7 +1078,7 @@ class FlpList(Collection[TItem], Generic[TItem]):
|
|
|
1071
1078
|
def zip(self, second: Iterable[TOther]) -> FlpIt[tuple[TItem, TOther]]: ...
|
|
1072
1079
|
|
|
1073
1080
|
@overload
|
|
1074
|
-
def zip(
|
|
1081
|
+
def zip[TResult, TOther](
|
|
1075
1082
|
self,
|
|
1076
1083
|
second: Iterable[TOther],
|
|
1077
1084
|
result_selector: Callable[[TItem, TOther], TResult],
|
|
@@ -1204,7 +1211,7 @@ class FlpList(Collection[TItem], Generic[TItem]):
|
|
|
1204
1211
|
|
|
1205
1212
|
raise EmptySequenceError() if predicate is _SENTINEL else NoMatchError()
|
|
1206
1213
|
|
|
1207
|
-
def first_or_default(
|
|
1214
|
+
def first_or_default[TResult](
|
|
1208
1215
|
self, default: TResult, predicate: Callable[[TItem], bool] | _Sentinel = _SENTINEL
|
|
1209
1216
|
) -> TItem | TResult:
|
|
1210
1217
|
return FlpIt(self.__list.data).first_or_default(default, predicate)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|