This release is a pre-release and may not be stable for production use.
FlpIt
Fluent LINQ for Python — LINQ semantics, Python-native execution
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.
Semantic goal:
flpstrives to faithfully reproduce .NET LINQ semantics.
Where Python's language or type system requires a different API contract, the contract is adapted accordingly. The implementation is actively developed, and semantic deviations may still exist.
⚡ Key Features
- ⚡ Lazy Evaluation (
FlpIt): Deferred, streaming execution where the underlying operation permits it.FlpItwraps a standard PythonIterable[T]. - 📦 Materialized Container (
FlpList): Eager, mutable container backed bycollections.UserList. - 🎯 LINQ Semantics: Operators such as
.any(),.first(),.single(),.order_by(), and.then_by()follow .NET LINQ semantics. - 🔀 Independent Query Pipelines: Derived queries are independent and never mutate or retroactively affect separately created queries.
- 🧠 Lazy Internal Materialization: Some operators, such as ordering, require internal materialization during deferred execution, while the query itself remains lazy.
- 🔹 Static-First Typing: Designed for precise
pyreflyinference without plugins (mypy and pyright might give varying results). - 🐍 Python-Native: Built on standard Python iterables, iterators, generators, and callable semantics.
🤸 Flipping the Pipeline
Native Python functional operations tend to produce an inside-out, right-to-left reading pattern...
# 🔁 Native Built-ins
result = list(map(lambda x: x * 10, filter(lambda x: x % 2 == 0, range(1, 11))))
# 🤸 FlpIt Pipeline
result = (
flp.it(range(1, 11))
.where(lambda x: x % 2 == 0)
.select(lambda x: x * 10)
.to_list()
)
💡 Quick Start
from flpit import flp, FlpIt, FlpList
# Deferred iterable via shorthand
data: FlpIt[int] = flp.it(range(1, 11))
# Lazy, streaming pipeline
query: FlpIt[int] = (
data
.where(lambda x: x % 2 == 0)
.select(lambda x: x * 10)
)
# Explicit materialization
result: FlpList[int] = query.to_list()
# [20, 40, 60, 80, 100]
# Or start directly with an eager container
eager_list: FlpList[int] = flp.lst([1, 2, 3, 4])
🔬 LINQ Semantics, Python Runtime
flpit deliberately separates the two:
- LINQ defines the query semantics.
- Python provides the underlying iterable and execution model.
For example:
query.any()
query.first()
query.single()
use LINQ semantics, while native Python operations remain Pythonic:
any(query)
list(query)
This provides a predictable LINQ vocabulary without attempting to replace Python's native iteration model.
🔗 Independent Queries
LINQ queries are composable without mutating the query they were derived from.
For example:
@dataclass
class Item: group: str; value: int
original = flp.it([Item("A", 2), Item("B", 1)])
ordered = original.order_by(lambda x: x.group)
extended = original.concat([Item("A", 1), Item("B", 2)])
ordered_by_group_and_value = ordered.then_by(lambda x: x.value)
The resulting query graph is:
original
│
├── order_by ──→ ordered
│ │
│ └── then_by ──→ ordered_by_group_and_value
│
└── concat ─────→ extended
ordered_by_group_and_value is derived from ordered.
It does not modify original, and it is not affected by the separately created extended query.
This matches the independent-query behavior of .NET LINQ.
🧠 Lazy Pipelines
Streaming operations do not materialize intermediate results:
query = (
flp.it(source)
.where(predicate)
.select(selector)
.order_by(key_selector)
.take(100)
)
Conceptually:
source
│
▼
where ← streaming
│
▼
select ← streaming
│
▼
order_by ← lazy, internally buffers when enumerated
│
▼
take ← streaming
│
▼
to_list ← consumer-facing materialization
Operations such as order_by() necessarily materialize their input internally to establish ordering.
⚙️ Installation
uv add flpit
📜 License
Distributed under the MIT License. See LICENSE for more information.
Release files for flpit 0.1.4.dev5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flpit-0.1.4.dev5.tar.gz | 10.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flpit-0.1.4.dev5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.4 kB
Release files / flpit-0.1.4.dev5.tar.gz
| Download URL | flpit-0.1.4.dev5.tar.gz |
|---|---|
| Size | 10.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f6ee49d8ed7797f7253e770487a04868a1106aa6ac23bd727ceb4e84b8c4e6a0
|
|
BLAKE2b-256 checksum How to use checksums |
b8546dc4298d4acca50e449e8a4b97cff2104d600812f1ec167bec6d4e024aef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / flpit-0.1.4.dev5-py3-none-any.whl
| Download URL | flpit-0.1.4.dev5-py3-none-any.whl |
|---|---|
| Size | 11.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bdff76270c653cf383baee03cfafda9c500c599743e34cb55c1f8c36ddac0c4e
|
|
BLAKE2b-256 checksum How to use checksums |
45cb0b95965aead1945ceb279ae16824dc55eb705169e4ee1489d647bf55816b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|