flpit 0.1.3__tar.gz → 0.1.4.dev7__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.
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 riemar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.5
2
+ Name: flpit
3
+ Version: 0.1.4.dev7
4
+ Summary: Fluent LINQ for Python — flip your iterables
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: fluent,generators,iterable,lazy-evaluation,linq,pipeline,pyrefly,type-safe
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+
21
+ # FlpIt
22
+
23
+ > **Fluent LINQ for Python — LINQ semantics, Python-native execution**
24
+
25
+ `FlpIt` brings a strongly typed, LINQ-style API to standard Python iterables. It combines the fluent query model and semantics of .NET LINQ with Python's native iterable and generator model.
26
+
27
+ > **Semantic goal:** `flp` strives to faithfully reproduce .NET LINQ semantics.
28
+ > Where Python's language or type system requires a different API contract, the contract is adapted accordingly.
29
+ > The implementation is actively developed, and semantic deviations may still exist.
30
+
31
+ ## ⚡ Key Features
32
+
33
+ * **⚡ Lazy Evaluation (`FlpIt`):** Deferred, streaming execution where the underlying operation permits it. `FlpIt` wraps a standard Python `Iterable[T]`.
34
+ * **📦 Materialized Container (`FlpList`):** Eager, mutable container backed by `collections.UserList`.
35
+ * **🎯 LINQ Semantics:** Operators such as `.any()`, `.first()`, `.single()`, `.order_by()`, and `.then_by()` follow .NET LINQ semantics.
36
+ * **🔀 Independent Query Pipelines:** Derived queries are independent and never mutate or retroactively affect separately created queries.
37
+ * **🧠 Lazy Internal Materialization:** Some operators, such as ordering, require internal materialization during deferred execution, while the query itself remains lazy.
38
+ * **🔹 Static-First Typing:** Designed for precise `pyrefly` inference without plugins (mypy and pyright might give varying results).
39
+ * **🐍 Python-Native:** Built on standard Python iterables, iterators, generators, and callable semantics.
40
+
41
+ ## 🤸 Flipping the Pipeline
42
+
43
+ Native Python functional operations tend to produce an inside-out, right-to-left reading pattern...
44
+
45
+ ```python
46
+ # 🔁 Native Built-ins
47
+ result = list(map(lambda x: x * 10, filter(lambda x: x % 2 == 0, range(1, 11))))
48
+
49
+ # 🤸 FlpIt Pipeline
50
+ result = (
51
+ flp.it(range(1, 11))
52
+ .where(lambda x: x % 2 == 0)
53
+ .select(lambda x: x * 10)
54
+ .to_list()
55
+ )
56
+ ```
57
+
58
+ ## 💡 Quick Start
59
+
60
+ ```python
61
+
62
+ from flpit import flp, FlpIt, FlpList
63
+
64
+ # Deferred iterable via shorthand
65
+ data: FlpIt[int] = flp.it(range(1, 11))
66
+
67
+ # Lazy, streaming pipeline
68
+ query: FlpIt[int] = (
69
+ data
70
+ .where(lambda x: x % 2 == 0)
71
+ .select(lambda x: x * 10)
72
+ )
73
+
74
+ # Explicit materialization
75
+ result: FlpList[int] = query.to_list()
76
+ # [20, 40, 60, 80, 100]
77
+
78
+ # Or start directly with an eager container
79
+ eager_list: FlpList[int] = flp.lst([1, 2, 3, 4])
80
+ ```
81
+
82
+ ## 🔬 LINQ Semantics, Python Runtime
83
+
84
+ `flpit` deliberately separates the two:
85
+
86
+ - **LINQ defines the query semantics.**
87
+ - **Python provides the underlying iterable and execution model.**
88
+
89
+ For example:
90
+
91
+ ```python
92
+ query.any()
93
+ query.first()
94
+ query.single()
95
+ ```
96
+
97
+ use LINQ semantics, while native Python operations remain Pythonic:
98
+
99
+ ```python
100
+ any(query)
101
+ list(query)
102
+ ```
103
+
104
+ This provides a predictable LINQ vocabulary without attempting to replace Python's native iteration model.
105
+
106
+ ## 🔗 Independent Queries
107
+
108
+ LINQ queries are composable without mutating the query they were derived from.
109
+
110
+ For example:
111
+
112
+ ```python
113
+ @dataclass
114
+ class Item: group: str; value: int
115
+
116
+ original = flp.it([Item("A", 2), Item("B", 1)])
117
+
118
+ ordered = original.order_by(lambda x: x.group)
119
+
120
+ extended = original.concat([Item("A", 1), Item("B", 2)])
121
+
122
+ ordered_by_group_and_value = ordered.then_by(lambda x: x.value)
123
+ ```
124
+
125
+ The resulting query graph is:
126
+
127
+ ```text
128
+ original
129
+ │
130
+ ├── order_by ──→ ordered
131
+ │ │
132
+ │ └── then_by ──→ ordered_by_group_and_value
133
+ │
134
+ └── concat ─────→ extended
135
+ ```
136
+
137
+ `ordered_by_group_and_value` is derived from `ordered`.
138
+ It does not modify `original`, and it is not affected by the separately created `extended` query.
139
+
140
+ This matches the independent-query behavior of .NET LINQ.
141
+
142
+ ## 🧠 Lazy Pipelines
143
+
144
+ Streaming operations do not materialize intermediate results:
145
+
146
+ ```python
147
+ query = (
148
+ flp.it(source)
149
+ .where(predicate)
150
+ .select(selector)
151
+ .order_by(key_selector)
152
+ .take(100)
153
+ )
154
+ ```
155
+
156
+ Conceptually:
157
+
158
+ ```text
159
+ source
160
+ │
161
+ ▼
162
+ where ← streaming
163
+ │
164
+ ▼
165
+ select ← streaming
166
+ │
167
+ ▼
168
+ order_by ← lazy, internally buffers when enumerated
169
+ │
170
+ ▼
171
+ take ← streaming
172
+ │
173
+ ▼
174
+ to_list ← consumer-facing materialization
175
+ ```
176
+
177
+ Operations such as `order_by()` necessarily materialize their input internally to establish ordering.
178
+
179
+ ## ⚙️ Installation
180
+
181
+ ```bash
182
+ uv add flpit
183
+ ```
184
+
185
+ ## 📜 License
186
+
187
+ Distributed under the MIT License. See `LICENSE` for more information.
@@ -0,0 +1,167 @@
1
+ # FlpIt
2
+
3
+ > **Fluent LINQ for Python — LINQ semantics, Python-native execution**
4
+
5
+ `FlpIt` brings a strongly typed, LINQ-style API to standard Python iterables. It combines the fluent query model and semantics of .NET LINQ with Python's native iterable and generator model.
6
+
7
+ > **Semantic goal:** `flp` strives to faithfully reproduce .NET LINQ semantics.
8
+ > Where Python's language or type system requires a different API contract, the contract is adapted accordingly.
9
+ > The implementation is actively developed, and semantic deviations may still exist.
10
+
11
+ ## ⚡ Key Features
12
+
13
+ * **⚡ Lazy Evaluation (`FlpIt`):** Deferred, streaming execution where the underlying operation permits it. `FlpIt` wraps a standard Python `Iterable[T]`.
14
+ * **📦 Materialized Container (`FlpList`):** Eager, mutable container backed by `collections.UserList`.
15
+ * **🎯 LINQ Semantics:** Operators such as `.any()`, `.first()`, `.single()`, `.order_by()`, and `.then_by()` follow .NET LINQ semantics.
16
+ * **🔀 Independent Query Pipelines:** Derived queries are independent and never mutate or retroactively affect separately created queries.
17
+ * **🧠 Lazy Internal Materialization:** Some operators, such as ordering, require internal materialization during deferred execution, while the query itself remains lazy.
18
+ * **🔹 Static-First Typing:** Designed for precise `pyrefly` inference without plugins (mypy and pyright might give varying results).
19
+ * **🐍 Python-Native:** Built on standard Python iterables, iterators, generators, and callable semantics.
20
+
21
+ ## 🤸 Flipping the Pipeline
22
+
23
+ Native Python functional operations tend to produce an inside-out, right-to-left reading pattern...
24
+
25
+ ```python
26
+ # 🔁 Native Built-ins
27
+ result = list(map(lambda x: x * 10, filter(lambda x: x % 2 == 0, range(1, 11))))
28
+
29
+ # 🤸 FlpIt Pipeline
30
+ result = (
31
+ flp.it(range(1, 11))
32
+ .where(lambda x: x % 2 == 0)
33
+ .select(lambda x: x * 10)
34
+ .to_list()
35
+ )
36
+ ```
37
+
38
+ ## 💡 Quick Start
39
+
40
+ ```python
41
+
42
+ from flpit import flp, FlpIt, FlpList
43
+
44
+ # Deferred iterable via shorthand
45
+ data: FlpIt[int] = flp.it(range(1, 11))
46
+
47
+ # Lazy, streaming pipeline
48
+ query: FlpIt[int] = (
49
+ data
50
+ .where(lambda x: x % 2 == 0)
51
+ .select(lambda x: x * 10)
52
+ )
53
+
54
+ # Explicit materialization
55
+ result: FlpList[int] = query.to_list()
56
+ # [20, 40, 60, 80, 100]
57
+
58
+ # Or start directly with an eager container
59
+ eager_list: FlpList[int] = flp.lst([1, 2, 3, 4])
60
+ ```
61
+
62
+ ## 🔬 LINQ Semantics, Python Runtime
63
+
64
+ `flpit` deliberately separates the two:
65
+
66
+ - **LINQ defines the query semantics.**
67
+ - **Python provides the underlying iterable and execution model.**
68
+
69
+ For example:
70
+
71
+ ```python
72
+ query.any()
73
+ query.first()
74
+ query.single()
75
+ ```
76
+
77
+ use LINQ semantics, while native Python operations remain Pythonic:
78
+
79
+ ```python
80
+ any(query)
81
+ list(query)
82
+ ```
83
+
84
+ This provides a predictable LINQ vocabulary without attempting to replace Python's native iteration model.
85
+
86
+ ## 🔗 Independent Queries
87
+
88
+ LINQ queries are composable without mutating the query they were derived from.
89
+
90
+ For example:
91
+
92
+ ```python
93
+ @dataclass
94
+ class Item: group: str; value: int
95
+
96
+ original = flp.it([Item("A", 2), Item("B", 1)])
97
+
98
+ ordered = original.order_by(lambda x: x.group)
99
+
100
+ extended = original.concat([Item("A", 1), Item("B", 2)])
101
+
102
+ ordered_by_group_and_value = ordered.then_by(lambda x: x.value)
103
+ ```
104
+
105
+ The resulting query graph is:
106
+
107
+ ```text
108
+ original
109
+ │
110
+ ├── order_by ──→ ordered
111
+ │ │
112
+ │ └── then_by ──→ ordered_by_group_and_value
113
+ │
114
+ └── concat ─────→ extended
115
+ ```
116
+
117
+ `ordered_by_group_and_value` is derived from `ordered`.
118
+ It does not modify `original`, and it is not affected by the separately created `extended` query.
119
+
120
+ This matches the independent-query behavior of .NET LINQ.
121
+
122
+ ## 🧠 Lazy Pipelines
123
+
124
+ Streaming operations do not materialize intermediate results:
125
+
126
+ ```python
127
+ query = (
128
+ flp.it(source)
129
+ .where(predicate)
130
+ .select(selector)
131
+ .order_by(key_selector)
132
+ .take(100)
133
+ )
134
+ ```
135
+
136
+ Conceptually:
137
+
138
+ ```text
139
+ source
140
+ │
141
+ ▼
142
+ where ← streaming
143
+ │
144
+ ▼
145
+ select ← streaming
146
+ │
147
+ ▼
148
+ order_by ← lazy, internally buffers when enumerated
149
+ │
150
+ ▼
151
+ take ← streaming
152
+ │
153
+ ▼
154
+ to_list ← consumer-facing materialization
155
+ ```
156
+
157
+ Operations such as `order_by()` necessarily materialize their input internally to establish ordering.
158
+
159
+ ## ⚙️ Installation
160
+
161
+ ```bash
162
+ uv add flpit
163
+ ```
164
+
165
+ ## 📜 License
166
+
167
+ Distributed under the MIT License. See `LICENSE` for more information.
@@ -1,10 +1,11 @@
1
1
  [project]
2
2
  name = "flpit"
3
- version = "0.1.3"
3
+ version = "0.1.4.dev7"
4
4
  description = "Fluent LINQ for Python — flip your iterables"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  license = { text = "MIT" }
8
+ keywords = ["linq", "fluent", "iterable", "pipeline", "lazy-evaluation", "pyrefly", "type-safe", "generators"]
8
9
  classifiers = [
9
10
  "Development Status :: 3 - Alpha",
10
11
  "Intended Audience :: Developers",
@@ -13,7 +14,9 @@ classifiers = [
13
14
  "Programming Language :: Python :: 3.11",
14
15
  "Programming Language :: Python :: 3.12",
15
16
  "Programming Language :: Python :: 3.13",
17
+ "Programming Language :: Python :: 3.14",
16
18
  "Topic :: Software Development :: Libraries :: Python Modules",
19
+ "Typing :: Typed"
17
20
  ]
18
21
  dependencies = []
19
22
 
@@ -26,24 +29,33 @@ dev = [
26
29
  "pytest-benchmark>=5.3.0",
27
30
  ]
28
31
 
32
+
29
33
  [build-system]
30
- requires = ["uv_build>=0.12.10,<0.13.0"]
31
- build-backend = "uv_build"
34
+ requires = ["hatchling"]
35
+ build-backend = "hatchling.build"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ # This tells Hatchling exactly which folders to grab
39
+ packages = ["src/flp", "src/flpit"]
40
+
41
+ [tool.hatch.build.targets.sdist]
42
+ # This keeps the exact working sdist inclusion rule you had earlier
43
+ include = ["src/flp", "src/flpit"]
32
44
 
33
- [tool.uv.build-backend]
34
- module-name = "flp"
35
- module-root = "src"
36
45
 
37
- [tool.pyright]
38
- include = ["src"]
39
- exclude = ["**/__pycache__", "tests"]
40
- typeCheckingMode = "basic"
46
+ [tool.pyrefly]
47
+ project-includes = ["src"]
48
+ project-excludes = ["**/__pycache__", "tests"]
49
+ search-path = ["src"]
50
+ preset = "strict"
41
51
 
42
52
  [tool.pytest.ini_options]
53
+ pythonpath = [
54
+ "src",
55
+ "tests"
56
+ ]
43
57
  addopts = "-m 'not benchmark'"
44
58
  markers = [
45
59
  "benchmark: performance benchmarks",
46
60
  ]
47
- #[tool.pyrefly]
48
- #extra-paths = ["src", "tests"]
49
- #python-interpreter-path = ".venv/bin/python" # or .venv/Scripts/python.exe on Windows
61
+
@@ -0,0 +1,21 @@
1
+ import warnings
2
+ import flpit # The NEW location
3
+
4
+ # Enforce a loud, clear deprecation alert
5
+ warnings.warn(
6
+ "The top-level 'flp' namespace is deprecated and has been moved to 'flpit'. "
7
+ "Please update your imports to 'from flpit import flp, FlpIt, FlpList'"
8
+ "This legacy entry point will throw a hard ImportError in a future version.",
9
+ DeprecationWarning,
10
+ stacklevel=2
11
+ )
12
+
13
+ __all__ = getattr(flpit.flp, "__all__", []) + getattr(flpit, "__all__", [])
14
+ mods = [flpit, flpit.flp]
15
+
16
+ def __getattr__(name: str):
17
+ for mod in mods:
18
+ if hasattr(mod, name):
19
+ return getattr(mod, name)
20
+
21
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
@@ -0,0 +1,37 @@
1
+ """
2
+ Fluent LINQ for Python — flip your iterables.
3
+ """
4
+ from __future__ import annotations
5
+ from importlib.metadata import version
6
+ __version__ = version("flpit")
7
+
8
+ import builtins
9
+ from typing import Iterable as _Iterable, TypeVar
10
+
11
+ from flpit import flp
12
+ from flpit.core.linq import (
13
+ FlpIt,
14
+ FlpList,
15
+ Grouping,
16
+ OrderedIt,
17
+ EmptySequenceError,
18
+ NoMatchError,
19
+ MultipleElementsError,
20
+ MultipleMatchesError,
21
+ SourceNoneError,
22
+ PredicateNoneError,
23
+ )
24
+
25
+ __all__ = [
26
+ "FlpIt",
27
+ "FlpList",
28
+ "Grouping",
29
+ "OrderedIt",
30
+ "EmptySequenceError",
31
+ "NoMatchError",
32
+ "MultipleElementsError",
33
+ "MultipleMatchesError",
34
+ "SourceNoneError",
35
+ "PredicateNoneError",
36
+ ]
37
+
@@ -1,8 +1,6 @@
1
1
  from __future__ import annotations
2
2
  from threading import Lock
3
3
 
4
- from typing import cast
5
-
6
4
  import builtins
7
5
  from collections import UserList
8
6
  from functools import wraps
@@ -20,6 +18,7 @@ from typing import (
20
18
  Type,
21
19
  TypeVar,
22
20
  Union,
21
+ override,
23
22
  overload,
24
23
  cast as typing_cast,
25
24
  )
@@ -35,26 +34,34 @@ _MISSING = object()
35
34
 
36
35
 
37
36
  class EmptySequenceError(ValueError):
38
- def __init__(self, message="Sequence contains no elements"):
39
- super().__init__(message)
37
+ def __init__(self):
38
+ super().__init__("Sequence contains no elements")
40
39
 
41
40
  class NoMatchError(ValueError):
42
- def __init__(self, message="Sequence contains no matching elements"):
43
- super().__init__(message)
41
+ def __init__(self):
42
+ super().__init__("Sequence contains no matching elements")
44
43
 
45
44
  class MultipleMatchesError(ValueError):
46
- def __init__(self, message="Sequence contains more than one matching element"):
47
- super().__init__(message)
45
+ def __init__(self):
46
+ super().__init__("Sequence contains more than one matching element")
48
47
 
49
48
  class MultipleElementsError(ValueError):
50
- def __init__(self, message="Sequence contains more than one element"):
51
- super().__init__(message)
49
+ def __init__(self):
50
+ super().__init__("Sequence contains more than one element")
51
+
52
+ class SourceNoneError(TypeError):
53
+ def __init__(self):
54
+ super().__init__("Value cannot be None. (Argument 'source')")
55
+
56
+ class PredicateNoneError(TypeError):
57
+ def __init__(self):
58
+ super().__init__("Value cannot be None. (Argument 'predicate')")
52
59
 
53
60
 
54
61
  def _guard_empty(func: Callable[..., Any]) -> Callable[..., Any]:
55
62
  """Catches Python's native empty-sequence ValueError and re-raises LINQ-compliant error."""
56
63
  @wraps(func)
57
- def wrapper(self, *args: Any, **kwargs: Any) -> Any:
64
+ def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
58
65
  try:
59
66
  return func(self, *args, **kwargs)
60
67
  except ValueError as e:
@@ -91,8 +98,10 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
91
98
  """
92
99
  __slots__ = ("_iterable",)
93
100
 
94
- def __init__(self, iterable: Iterable[TItem]) -> None:
95
- self._iterable: Iterable[TItem] = iterable
101
+ def __init__(self, source: Iterable[TItem]) -> None:
102
+ if source is None:
103
+ raise SourceNoneError()
104
+ self._iterable: Iterable[TItem] = source
96
105
 
97
106
  def __iter__(self) -> Iterator[TItem]:
98
107
  return iter(self._iterable)
@@ -372,7 +381,7 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
372
381
 
373
382
  def last(
374
383
  self,
375
- predicate: Optional[Callable[[TItem], bool]] = None,
384
+ predicate: Optional[Callable[[TItem], bool]] = _SENTINEL,
376
385
  ) -> TItem:
377
386
  """
378
387
  Returns the last element of the sequence, or the last element
@@ -385,24 +394,27 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
385
394
  Raises EmptySequenceError if the sequence is empty or if no
386
395
  element satisfies the predicate.
387
396
  """
388
- last_item: TItem | object = _SENTINEL
397
+ if predicate is None:
398
+ raise PredicateNoneError()
399
+
400
+ last_item: TItem | object = _MISSING
389
401
 
390
402
  for item in self:
391
- if predicate is None or predicate(item):
403
+ if predicate is _SENTINEL or predicate(item):
392
404
  last_item = item
393
405
 
394
- if last_item is _SENTINEL:
395
- raise EmptySequenceError() if predicate is None else NoMatchError()
406
+ if last_item is _MISSING:
407
+ raise EmptySequenceError() if predicate is _SENTINEL else NoMatchError()
396
408
 
397
- return cast(TItem, last_item)
409
+ return typing_cast(TItem, last_item)
398
410
 
399
- def _extreme(self, func, **kwargs) -> TItem:
411
+ def _extreme(self, func: Any, **kwargs: Any) -> TItem:
400
412
  result = func(self, default=_MISSING, **kwargs)
401
413
 
402
414
  if result is _MISSING:
403
415
  raise EmptySequenceError()
404
416
 
405
- return cast(TItem, result)
417
+ return typing_cast(TItem, result)
406
418
 
407
419
  def min(self) -> TItem:
408
420
  """Returns the minimum value in a sequence."""
@@ -487,6 +499,7 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
487
499
  except (EmptySequenceError, NoMatchError):
488
500
  return default
489
501
 
502
+ # pyrefly: ignore [bad-function-definition]
490
503
  def single(self, predicate: Optional[Callable[[TItem], bool]] = _SENTINEL) -> TItem:
491
504
  """Returns the single, unique element matching a predicate."""
492
505
  if predicate is None:
@@ -514,9 +527,6 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
514
527
  return FlpList(self)
515
528
 
516
529
 
517
- _OrderCriterion = tuple[Callable[[TItem], Any], bool]
518
-
519
-
520
530
  class _OrderState(Generic[TItem]):
521
531
  """
522
532
  Shared lazy snapshot for an OrderBy/ThenBy chain.
@@ -580,8 +590,9 @@ class OrderedIt(FlpIt[TItem]):
580
590
  # Pass () to FlpIt to prevent storing a permanent reference to the raw source
581
591
  super().__init__(())
582
592
 
593
+
583
594
  self._state = _OrderState(source)
584
- self._criteria: tuple[_OrderCriterion, ...] = (
595
+ self._criteria: tuple[tuple[Callable[[TItem], Any], bool], ...] = (
585
596
  (key_selector, descending),
586
597
  )
587
598
 
@@ -636,6 +647,7 @@ class OrderedIt(FlpIt[TItem]):
636
647
  descending=True,
637
648
  )
638
649
 
650
+ @override
639
651
  def __iter__(self) -> Iterator[TItem]:
640
652
  cached = self._cached_result
641
653
 
@@ -690,6 +702,12 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
690
702
  | Fluent List
691
703
  A materialized list extending UserList that yields lazy FlpIt instances for query operations.
692
704
  """
705
+ def __init__(self, source: TItem=_SENTINEL):
706
+ if source is None:
707
+ raise SourceNoneError()
708
+ if source is _SENTINEL:
709
+ source = [] # don't depend on the base class to do the right thing
710
+ super().__init__(source)
693
711
 
694
712
  def add(self, item: TItem) -> None:
695
713
  """
@@ -802,6 +820,7 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
802
820
  second: Iterable[TOther],
803
821
  result_selector: Optional[Callable[[TItem, TOther], Any]] = None,
804
822
  ) -> FlpIt[Any]:
823
+ # pyrefly: ignore [bad-argument-type] # yeah well if overloads are just pretend
805
824
  return FlpIt(self.data).zip(second, result_selector)
806
825
 
807
826
  def chunk(self, size: int) -> FlpIt[FlpList[TItem]]:
@@ -839,7 +858,7 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
839
858
 
840
859
  @_guard_empty
841
860
  def min(self) -> TItem:
842
- return builtins.min(self.data)
861
+ return builtins.min(self.data) # pyrefly: ignore [bad-specialization]
843
862
 
844
863
  @_guard_empty
845
864
  def min_by(self, key_selector: Callable[[TItem], Any]) -> TItem:
@@ -847,7 +866,7 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
847
866
 
848
867
  @_guard_empty
849
868
  def max(self) -> TItem:
850
- return builtins.max(self.data)
869
+ return builtins.max(self.data) # pyrefly: ignore [bad-specialization]
851
870
 
852
871
  @_guard_empty
853
872
  def max_by(self, key_selector: Callable[[TItem], Any]) -> TItem:
@@ -866,7 +885,7 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
866
885
  return FlpIt(self.data).average(selector)
867
886
 
868
887
  # --- Overloads for the Type System ---
869
- # 1. Native compatibility path (MUST be first): exact value match
888
+ # 1. Native Python UserList compatibility path (MUST be first): exact value match
870
889
  @overload
871
890
  def count(self, item: TItem) -> int: ...
872
891
 
@@ -880,8 +899,24 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
880
899
 
881
900
  # --- The Clean Implementation ---
882
901
  # We name the parameter 'item' to perfectly match UserList, but default it to None
902
+ @override
883
903
  def count(self, item: Any = _SENTINEL) -> int:
884
- """Counts elements in the list matching an optional predicate or exact value."""
904
+ """
905
+ Counts elements using Python/UserList or .NET LINQ conventions.
906
+
907
+ Supported forms:
908
+
909
+ count() -> count all elements (.NET LINQ)
910
+ count(predicate) -> count elements matching predicate (.NET LINQ)
911
+ count(value) -> count occurrences of an exact value (Python)
912
+
913
+ Note:
914
+ Because Python callables are also valid list values, ``count(value)``
915
+ is inherently ambiguous when ``value`` is callable. A callable
916
+ argument is interpreted as a LINQ predicate, so counting occurrences
917
+ of a callable object via the native ``UserList.count(value)`` behavior
918
+ is not distinguishable from ``count(predicate)``.
919
+ """
885
920
  # Scenario C: No argument passed (.count()) -> Return total length
886
921
  if item is _SENTINEL:
887
922
  return len(self.data)
@@ -910,7 +945,7 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
910
945
 
911
946
  def last(
912
947
  self,
913
- predicate: Optional[Callable[[TItem], bool]] = None,
948
+ predicate: Optional[Callable[[TItem], bool]] = _SENTINEL,
914
949
  ) -> TItem:
915
950
  """
916
951
  Returns the last element of the sequence, or the last element
@@ -924,6 +959,9 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
924
959
  element satisfies the predicate.
925
960
  """
926
961
  if predicate is None:
962
+ raise PredicateNoneError()
963
+
964
+ if predicate is _SENTINEL:
927
965
  if not self.data:
928
966
  raise EmptySequenceError()
929
967
  return self.data[-1]
@@ -932,13 +970,14 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
932
970
  if predicate(item):
933
971
  return item
934
972
 
935
- raise NoMatchError()
973
+ raise EmptySequenceError() if predicate is None else NoMatchError()
936
974
 
937
975
  def first_or_default(
938
976
  self, default: TResult, predicate: Optional[Callable[[TItem], bool]] = None
939
977
  ) -> Union[TItem, TResult]:
940
978
  return FlpIt(self.data).first_or_default(default, predicate)
941
979
 
980
+ # pyrefly: ignore [bad-function-definition]
942
981
  def single(self, predicate: Optional[Callable[[TItem], bool]] = _SENTINEL) -> TItem:
943
982
  return FlpIt(self.data).single(predicate)
944
983
 
@@ -3,12 +3,11 @@ Fluent LINQ for Python — flip your iterables.
3
3
  """
4
4
  from __future__ import annotations
5
5
  from importlib.metadata import version
6
- __version__ = version("flpit")
7
6
 
8
7
  import builtins
9
8
  from typing import Iterable as _Iterable, TypeVar
10
9
 
11
- from flp.core.linq import Grouping, FlpList, FlpIt, OrderedIt
10
+ from flpit.core.linq import FlpList, FlpIt
12
11
 
13
12
  TItem = TypeVar("TItem")
14
13
 
@@ -30,10 +29,6 @@ def repeat(element: TItem, count: int) -> FlpIt[TItem]:
30
29
  return FlpIt(element for _ in builtins.range(count))
31
30
 
32
31
  __all__ = [
33
- "FlpIt",
34
- "OrderedIt",
35
- "Grouping",
36
- "FlpList",
37
32
  "it",
38
33
  "lst",
39
34
  "range",
flpit-0.1.3/PKG-INFO DELETED
@@ -1,61 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: flpit
3
- Version: 0.1.3
4
- Summary: Fluent LINQ for Python — flip your iterables
5
- License: MIT
6
- Classifier: Development Status :: 3 - Alpha
7
- Classifier: Intended Audience :: Developers
8
- Classifier: License :: OSI Approved :: MIT License
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.11
11
- Classifier: Programming Language :: Python :: 3.12
12
- Classifier: Programming Language :: Python :: 3.13
13
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
- Requires-Python: >=3.11
15
- Description-Content-Type: text/markdown
16
-
17
- # flp
18
-
19
- > **Fluent LINQ for Python – flip your iterables**
20
-
21
- `flp` brings a LINQ-inspired fluent API to standard Python iterables with full static typing.
22
-
23
- ## ⚡ Key Features
24
-
25
- * **⚡ Lazy Evaluation (`FlpIt`):** Deferred execution using Python iterators and generators.
26
- * **📦 Materialized Container (`FlpList`):** Eager, mutable container backed by `collections.UserList`.
27
- * **🔹 Static-First Typing:** Built for full `mypy` and `pyright` inference without plugins.
28
- * **🔄 No Implicit Caching:** Query results are not cached unless explicitly documented, such as `order_by`.
29
-
30
- ## ⚙️ Installation
31
-
32
- ```bash
33
- uv add flpit
34
- ```
35
-
36
- ## 💡 Quick Start
37
-
38
- ```python
39
- import flp
40
- from flp import FlpIt, FlpList
41
-
42
- # Deferred iterable via shorthand
43
- data: FlpIt[int] = flp.it([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
44
-
45
- # Deferred / lazy pipeline
46
- query: FlpIt[int] = (
47
- data
48
- .where(lambda x: x % 2 == 0)
49
- .select(lambda x: x * 10)
50
- )
51
-
52
- # Materialize query results explicitly
53
- result: FlpList[int] = query.to_list() # [20, 40, 60, 80, 100]
54
-
55
- # Or start directly with an eager container
56
- eager_list: FlpList[int] = flp.lst([1, 2, 3, 4])
57
- ```
58
-
59
- ## 📜 License
60
-
61
- Distributed under the MIT License. See `LICENSE` for more information.
flpit-0.1.3/README.md DELETED
@@ -1,45 +0,0 @@
1
- # flp
2
-
3
- > **Fluent LINQ for Python – flip your iterables**
4
-
5
- `flp` brings a LINQ-inspired fluent API to standard Python iterables with full static typing.
6
-
7
- ## ⚡ Key Features
8
-
9
- * **⚡ Lazy Evaluation (`FlpIt`):** Deferred execution using Python iterators and generators.
10
- * **📦 Materialized Container (`FlpList`):** Eager, mutable container backed by `collections.UserList`.
11
- * **🔹 Static-First Typing:** Built for full `mypy` and `pyright` inference without plugins.
12
- * **🔄 No Implicit Caching:** Query results are not cached unless explicitly documented, such as `order_by`.
13
-
14
- ## ⚙️ Installation
15
-
16
- ```bash
17
- uv add flpit
18
- ```
19
-
20
- ## 💡 Quick Start
21
-
22
- ```python
23
- import flp
24
- from flp import FlpIt, FlpList
25
-
26
- # Deferred iterable via shorthand
27
- data: FlpIt[int] = flp.it([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
28
-
29
- # Deferred / lazy pipeline
30
- query: FlpIt[int] = (
31
- data
32
- .where(lambda x: x % 2 == 0)
33
- .select(lambda x: x * 10)
34
- )
35
-
36
- # Materialize query results explicitly
37
- result: FlpList[int] = query.to_list() # [20, 40, 60, 80, 100]
38
-
39
- # Or start directly with an eager container
40
- eager_list: FlpList[int] = flp.lst([1, 2, 3, 4])
41
- ```
42
-
43
- ## 📜 License
44
-
45
- Distributed under the MIT License. See `LICENSE` for more information.
@@ -1,49 +0,0 @@
1
- [project]
2
- name = "flpit"
3
- version = "0.1.3"
4
- description = "Fluent LINQ for Python — flip your iterables"
5
- readme = "README.md"
6
- requires-python = ">=3.11"
7
- classifiers = [
8
- "Development Status :: 3 - Alpha",
9
- "Intended Audience :: Developers",
10
- "License :: OSI Approved :: MIT License",
11
- "Programming Language :: Python :: 3",
12
- "Programming Language :: Python :: 3.11",
13
- "Programming Language :: Python :: 3.12",
14
- "Programming Language :: Python :: 3.13",
15
- "Topic :: Software Development :: Libraries :: Python Modules",
16
- ]
17
- dependencies = []
18
-
19
- [project.license]
20
- text = "MIT"
21
-
22
- [dependency-groups]
23
- dev = [
24
- "pytest>=8.0",
25
- "hypothesis>=6.168.0",
26
- "pyrefly>=1.3.1",
27
- "pyright>=1.1.414",
28
- "pytest-benchmark>=5.3.0",
29
- ]
30
-
31
- [build-system]
32
- requires = ["uv_build>=0.12.10,<0.13.0"]
33
- build-backend = "uv_build"
34
-
35
- [tool.uv.build-backend]
36
- module-name = "flp"
37
- module-root = "src"
38
-
39
- [tool.pyright]
40
- include = ["src"]
41
- exclude = [
42
- "**/__pycache__",
43
- "tests",
44
- ]
45
- typeCheckingMode = "basic"
46
-
47
- [tool.pytest.ini_options]
48
- addopts = "-m 'not benchmark'"
49
- markers = ["benchmark: performance benchmarks"]