flpit 0.2.4__tar.gz → 0.3.1__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.4 → flpit-0.3.1}/PKG-INFO +96 -34
- {flpit-0.2.4 → flpit-0.3.1}/README.md +95 -33
- {flpit-0.2.4 → flpit-0.3.1}/pyproject.toml +1 -1
- {flpit-0.2.4 → flpit-0.3.1}/src/flpit/__init__.py +21 -3
- flpit-0.3.1/src/flpit/core/_grouping.py +29 -0
- flpit-0.3.1/src/flpit/core/_join.py +7 -0
- flpit-0.3.1/src/flpit/core/_zip.py +13 -0
- {flpit-0.2.4 → flpit-0.3.1}/src/flpit/core/linq.py +741 -109
- {flpit-0.2.4 → flpit-0.3.1}/src/flpit/csv.py +0 -145
- {flpit-0.2.4 → flpit-0.3.1}/src/flpit/task.py +389 -59
- flpit-0.3.1/src/flpit/threading.py +171 -0
- {flpit-0.2.4 → flpit-0.3.1}/.gitignore +0 -0
- {flpit-0.2.4 → flpit-0.3.1}/LICENSE +0 -0
- {flpit-0.2.4 → flpit-0.3.1}/src/flp/__init__.py +0 -0
- {flpit-0.2.4 → flpit-0.3.1}/src/flpit/core/__init__.py +0 -0
- {flpit-0.2.4 → flpit-0.3.1}/src/flpit/flp.py +0 -0
- {flpit-0.2.4 → flpit-0.3.1}/src/flpit/json.py +0 -0
- {flpit-0.2.4 → flpit-0.3.1}/src/flpit/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: flpit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Fluent LINQ for Python — flip your iterables
|
|
5
5
|
Project-URL: Homepage, https://github.com/riemar/flpit
|
|
6
6
|
Project-URL: Repository, https://github.com/riemar/flpit
|
|
@@ -24,6 +24,9 @@ Description-Content-Type: text/markdown
|
|
|
24
24
|
|
|
25
25
|
# FlpIt
|
|
26
26
|
|
|
27
|
+
**[🌐 View on GitHub](https://github.com/riemar/flpit)** | [📦 View on PyPI](https://pypi.org/project/flpit/)
|
|
28
|
+
|
|
29
|
+
|
|
27
30
|
> **Fluent LINQ for Python — LINQ semantics, Python-native execution**
|
|
28
31
|
|
|
29
32
|
`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.
|
|
@@ -32,17 +35,14 @@ Description-Content-Type: text/markdown
|
|
|
32
35
|
> Where Python's language or type system requires a different API contract, the contract is adapted accordingly.
|
|
33
36
|
> The implementation is actively developed, and semantic deviations may still exist.
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
> **`List<T>` semantics:** `add()`, `add_range()`, and other mutating operations follow the `List<T>` API where applicable.
|
|
44
|
-
>
|
|
45
|
-
> **Removed:** `copy()`, `+`, `*`, and other `UserList`-specific operations that are not part of the `FlpList` API.
|
|
38
|
+
## Familiar LINQ, Now in Python
|
|
39
|
+
|
|
40
|
+
FlpIt is a Python implementation of the popular .NET Language Integrated Query (LINQ), adapted to Python.
|
|
41
|
+
|
|
42
|
+
If you know C# LINQ, you already know most of FlpIt. Existing LINQ examples, query patterns, and documentation can generally be translated directly.
|
|
43
|
+
FlpIt aims to make its implemented LINQ operators behave like their corresponding .NET `System.Linq.Enumerable` counterparts.
|
|
44
|
+
|
|
45
|
+
This makes the extensive .NET LINQ ecosystem a valuable reference for developers and AI coding assistants alike.
|
|
46
46
|
|
|
47
47
|
## ⚡ Key Features
|
|
48
48
|
|
|
@@ -311,6 +311,30 @@ It does not modify `original`, and it is not affected by the separately created
|
|
|
311
311
|
|
|
312
312
|
This matches the independent-query behavior of .NET LINQ.
|
|
313
313
|
|
|
314
|
+
### Joins
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
customers = [("Prakash", 1), ("Tim", 2), ("Robert", 3)]
|
|
318
|
+
orders = [(10, 1), (11, 1), (12, 4)] # (order_id, customer_id)
|
|
319
|
+
|
|
320
|
+
flp.it(customers).join(orders, lambda c: c[1], lambda o: o[1], lambda c, o: (c[0], o[0]))
|
|
321
|
+
# [("Prakash", 10), ("Prakash", 11)] inner join (.NET Join)
|
|
322
|
+
|
|
323
|
+
flp.it(customers).left_join(orders, lambda c: c[1], lambda o: o[1], lambda c, o: (c[0], o and o[0]))
|
|
324
|
+
# [("Prakash", 10), ("Prakash", 11), ("Tim", None), ("Robert", None)] (.NET 10 LeftJoin)
|
|
325
|
+
|
|
326
|
+
flp.it(customers).full_join(orders, lambda c: c[1], lambda o: o[1], lambda c, o: (c and c[0], o and o[0]))
|
|
327
|
+
# [("Prakash", 10), ("Prakash", 11), ("Tim", None), ("Robert", None), (None, 12)]
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Without a result selector all three yield `JoinTuple(outer, inner)`.
|
|
331
|
+
|
|
332
|
+
* Elements whose key is `None` never match. `left_join()` / `full_join()` still emit them as unmatched.
|
|
333
|
+
* An unmatched side is passed to the result selector as `None` (the Python equivalent of .NET `default`).
|
|
334
|
+
* `full_join()` has no .NET LINQ equivalent. It yields the `left_join()` rows in outer order, followed by every inner element that was never matched (in inner order).
|
|
335
|
+
* Argument validation is eager (`InnerNoneError`, `OuterKeySelectorNoneError`, `InnerKeySelectorNoneError`, `ResultSelectorNoneError`); everything else is deferred.
|
|
336
|
+
* Custom equality comparers are not supported yet (keys are compared with `==` / `hash`, like `group_by()`).
|
|
337
|
+
|
|
314
338
|
## Execution Characteristics
|
|
315
339
|
|
|
316
340
|
Lazy execution does not mean that every operator is streaming. Some operators must consume and buffer their input before they can produce their result.
|
|
@@ -321,9 +345,14 @@ Lazy execution does not mean that every operator is streaming. Some operators mu
|
|
|
321
345
|
| `where()` / `select()` | Yes | Yes | No |
|
|
322
346
|
| `order_by()` | Yes | No | Yes |
|
|
323
347
|
| `group_by()` | Yes | No | Yes |
|
|
348
|
+
| `join()` | Yes | Yes (outer) | Yes (inner) |
|
|
349
|
+
| `left_join()` | Yes | Yes (outer) | Yes (inner) |
|
|
350
|
+
| `full_join()` | Yes | Yes (outer) | Yes (inner) |
|
|
324
351
|
|
|
325
352
|
`order_by()` and `group_by()` therefore remain deferred at query construction time, but enumeration of either operator requires buffering its upstream input.
|
|
326
353
|
|
|
354
|
+
`join()`, `left_join()` and `full_join()` build a hash lookup of the `inner` sequence when enumeration starts and stream the outer sequence (O(N + M)).
|
|
355
|
+
|
|
327
356
|
`group_by()` produces a query of groupings rather than terminating the pipeline:
|
|
328
357
|
|
|
329
358
|
```python
|
|
@@ -405,8 +434,13 @@ Key features:
|
|
|
405
434
|
- **Task coordination:** `Task.when_all()` waits for all tasks while preserving input order; `Task.when_any()` returns the first completed task; `Task.collect()` yields tasks as they complete.
|
|
406
435
|
- **Cancellation:** `CancellationToken` and `CancellationTokenSource` support cooperative cancellation, cancellation callbacks, timeouts, and linked cancellation tokens.
|
|
407
436
|
- **Resource management:** Configure worker capacity and pending background-work limits, handle queue saturation explicitly, and shut down the process-wide runtime with an observable shutdown report.
|
|
437
|
+
- **Asyncio interoperability:** FlpIt Tasks can be awaited directly inside asyncio coroutines when Task.asyncio_interop is enabled.
|
|
438
|
+
- This is disabled by default.
|
|
439
|
+
- Enabling it adds runtime-detection overhead to every pending await.
|
|
440
|
+
- await in flpit runtime: 200ish nano sec (just loop detection)
|
|
441
|
+
- await in asyncio loop:
|
|
408
442
|
|
|
409
|
-
The runtime starts lazily and manages its own lifecycle.
|
|
443
|
+
The runtime starts lazily and manages its own lifecycle. Cancellation of running synchronous functions is cooperative.
|
|
410
444
|
|
|
411
445
|
See the [Task Runtime examples](https://github.com/riemar/flpit/tree/main/examples/task) for usage examples.
|
|
412
446
|
|
|
@@ -427,25 +461,53 @@ Distributed under the MIT License. See `LICENSE` for more information.
|
|
|
427
461
|
> Note: The test cases exercise both core types, `FlpIt` and `FlpList`, with transitions between lazy and eager execution occurring as part of the individual test cases.
|
|
428
462
|
|
|
429
463
|
```
|
|
430
|
-
|
|
431
|
-
| Test Module File
|
|
432
|
-
|
|
433
|
-
| tests/nettests/
|
|
434
|
-
| ├──
|
|
435
|
-
| ├──
|
|
436
|
-
| ├──
|
|
437
|
-
| ├──
|
|
438
|
-
| ├──
|
|
439
|
-
| ├──
|
|
440
|
-
| ├──
|
|
441
|
-
| ├──
|
|
442
|
-
| ├──
|
|
443
|
-
| ├──
|
|
444
|
-
| ├──
|
|
445
|
-
| ├──
|
|
446
|
-
| ├──
|
|
447
|
-
|
|
|
448
|
-
|
|
449
|
-
|
|
|
450
|
-
|
|
464
|
+
================================================================================
|
|
465
|
+
| Test Module File | Test Cases |
|
|
466
|
+
--------------------------------------------------------------------------------
|
|
467
|
+
| tests/nettests/ | |
|
|
468
|
+
| ├── linq/ | |
|
|
469
|
+
| │ ├── test_aggregate.py | 42 |
|
|
470
|
+
| │ ├── test_all.py | 80 |
|
|
471
|
+
| │ ├── test_any.py | 90 |
|
|
472
|
+
| │ ├── test_average.py | 572 |
|
|
473
|
+
| │ ├── test_chunk.py | 22 |
|
|
474
|
+
| │ ├── test_concat.py | 64 |
|
|
475
|
+
| │ ├── test_contains.py | 108 |
|
|
476
|
+
| │ ├── test_count.py | 37 |
|
|
477
|
+
| │ ├── test_distinct.py | 87 |
|
|
478
|
+
| │ ├── test_element_at.py | 43 |
|
|
479
|
+
| │ ├── test_first.py | 29 |
|
|
480
|
+
| │ ├── test_first_or_default.py | 50 |
|
|
481
|
+
| │ ├── test_full_join.py | 37 |
|
|
482
|
+
| │ ├── test_generic_list.py | 95 |
|
|
483
|
+
| │ ├── test_group_by.py | 23 |
|
|
484
|
+
| │ ├── test_join.py | 47 |
|
|
485
|
+
| │ ├── test_last.py | 29 |
|
|
486
|
+
| │ ├── test_last_or_default.py | 45 |
|
|
487
|
+
| │ ├── test_left_join.py | 47 |
|
|
488
|
+
| │ ├── test_max.py | 1100 |
|
|
489
|
+
| │ ├── test_min.py | 968 |
|
|
490
|
+
| │ ├── test_order_by.py | 100 |
|
|
491
|
+
| │ ├── test_order_descending_by.py | 45 |
|
|
492
|
+
| │ ├── test_reverse.py | 35 |
|
|
493
|
+
| │ ├── test_select.py | 140 |
|
|
494
|
+
| │ ├── test_select_many.py | 85 |
|
|
495
|
+
| │ ├── test_single.py | 34 |
|
|
496
|
+
| │ ├── test_single_or_default.py | 48 |
|
|
497
|
+
| │ ├── test_skip.py | 75 |
|
|
498
|
+
| │ ├── test_sum.py | 134 |
|
|
499
|
+
| │ ├── test_take.py | 39 |
|
|
500
|
+
| │ ├── test_then_by.py | 40 |
|
|
501
|
+
| │ ├── test_then_descending_by.py | 34 |
|
|
502
|
+
| │ ├── test_where.py | 76 |
|
|
503
|
+
| │ └── test_zip.py | 101 |
|
|
504
|
+
| └── task/ | |
|
|
505
|
+
| ├── test_cancellation.py | 69 |
|
|
506
|
+
| ├── test_task_completion_source.py | 26 |
|
|
507
|
+
| ├── test_task_factory.py | 3 |
|
|
508
|
+
| ├── test_task_rt.py | 12 |
|
|
509
|
+
| └── test_task_rt_core.py | 22 |
|
|
510
|
+
--------------------------------------------------------------------------------
|
|
511
|
+
| GRAND TOTALS | 4733 |
|
|
512
|
+
================================================================================
|
|
451
513
|
```
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# FlpIt
|
|
2
2
|
|
|
3
|
+
**[🌐 View on GitHub](https://github.com/riemar/flpit)** | [📦 View on PyPI](https://pypi.org/project/flpit/)
|
|
4
|
+
|
|
5
|
+
|
|
3
6
|
> **Fluent LINQ for Python — LINQ semantics, Python-native execution**
|
|
4
7
|
|
|
5
8
|
`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.
|
|
@@ -8,17 +11,14 @@
|
|
|
8
11
|
> Where Python's language or type system requires a different API contract, the contract is adapted accordingly.
|
|
9
12
|
> The implementation is actively developed, and semantic deviations may still exist.
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
> **`List<T>` semantics:** `add()`, `add_range()`, and other mutating operations follow the `List<T>` API where applicable.
|
|
20
|
-
>
|
|
21
|
-
> **Removed:** `copy()`, `+`, `*`, and other `UserList`-specific operations that are not part of the `FlpList` API.
|
|
14
|
+
## Familiar LINQ, Now in Python
|
|
15
|
+
|
|
16
|
+
FlpIt is a Python implementation of the popular .NET Language Integrated Query (LINQ), adapted to Python.
|
|
17
|
+
|
|
18
|
+
If you know C# LINQ, you already know most of FlpIt. Existing LINQ examples, query patterns, and documentation can generally be translated directly.
|
|
19
|
+
FlpIt aims to make its implemented LINQ operators behave like their corresponding .NET `System.Linq.Enumerable` counterparts.
|
|
20
|
+
|
|
21
|
+
This makes the extensive .NET LINQ ecosystem a valuable reference for developers and AI coding assistants alike.
|
|
22
22
|
|
|
23
23
|
## ⚡ Key Features
|
|
24
24
|
|
|
@@ -287,6 +287,30 @@ It does not modify `original`, and it is not affected by the separately created
|
|
|
287
287
|
|
|
288
288
|
This matches the independent-query behavior of .NET LINQ.
|
|
289
289
|
|
|
290
|
+
### Joins
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
customers = [("Prakash", 1), ("Tim", 2), ("Robert", 3)]
|
|
294
|
+
orders = [(10, 1), (11, 1), (12, 4)] # (order_id, customer_id)
|
|
295
|
+
|
|
296
|
+
flp.it(customers).join(orders, lambda c: c[1], lambda o: o[1], lambda c, o: (c[0], o[0]))
|
|
297
|
+
# [("Prakash", 10), ("Prakash", 11)] inner join (.NET Join)
|
|
298
|
+
|
|
299
|
+
flp.it(customers).left_join(orders, lambda c: c[1], lambda o: o[1], lambda c, o: (c[0], o and o[0]))
|
|
300
|
+
# [("Prakash", 10), ("Prakash", 11), ("Tim", None), ("Robert", None)] (.NET 10 LeftJoin)
|
|
301
|
+
|
|
302
|
+
flp.it(customers).full_join(orders, lambda c: c[1], lambda o: o[1], lambda c, o: (c and c[0], o and o[0]))
|
|
303
|
+
# [("Prakash", 10), ("Prakash", 11), ("Tim", None), ("Robert", None), (None, 12)]
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Without a result selector all three yield `JoinTuple(outer, inner)`.
|
|
307
|
+
|
|
308
|
+
* Elements whose key is `None` never match. `left_join()` / `full_join()` still emit them as unmatched.
|
|
309
|
+
* An unmatched side is passed to the result selector as `None` (the Python equivalent of .NET `default`).
|
|
310
|
+
* `full_join()` has no .NET LINQ equivalent. It yields the `left_join()` rows in outer order, followed by every inner element that was never matched (in inner order).
|
|
311
|
+
* Argument validation is eager (`InnerNoneError`, `OuterKeySelectorNoneError`, `InnerKeySelectorNoneError`, `ResultSelectorNoneError`); everything else is deferred.
|
|
312
|
+
* Custom equality comparers are not supported yet (keys are compared with `==` / `hash`, like `group_by()`).
|
|
313
|
+
|
|
290
314
|
## Execution Characteristics
|
|
291
315
|
|
|
292
316
|
Lazy execution does not mean that every operator is streaming. Some operators must consume and buffer their input before they can produce their result.
|
|
@@ -297,9 +321,14 @@ Lazy execution does not mean that every operator is streaming. Some operators mu
|
|
|
297
321
|
| `where()` / `select()` | Yes | Yes | No |
|
|
298
322
|
| `order_by()` | Yes | No | Yes |
|
|
299
323
|
| `group_by()` | Yes | No | Yes |
|
|
324
|
+
| `join()` | Yes | Yes (outer) | Yes (inner) |
|
|
325
|
+
| `left_join()` | Yes | Yes (outer) | Yes (inner) |
|
|
326
|
+
| `full_join()` | Yes | Yes (outer) | Yes (inner) |
|
|
300
327
|
|
|
301
328
|
`order_by()` and `group_by()` therefore remain deferred at query construction time, but enumeration of either operator requires buffering its upstream input.
|
|
302
329
|
|
|
330
|
+
`join()`, `left_join()` and `full_join()` build a hash lookup of the `inner` sequence when enumeration starts and stream the outer sequence (O(N + M)).
|
|
331
|
+
|
|
303
332
|
`group_by()` produces a query of groupings rather than terminating the pipeline:
|
|
304
333
|
|
|
305
334
|
```python
|
|
@@ -381,8 +410,13 @@ Key features:
|
|
|
381
410
|
- **Task coordination:** `Task.when_all()` waits for all tasks while preserving input order; `Task.when_any()` returns the first completed task; `Task.collect()` yields tasks as they complete.
|
|
382
411
|
- **Cancellation:** `CancellationToken` and `CancellationTokenSource` support cooperative cancellation, cancellation callbacks, timeouts, and linked cancellation tokens.
|
|
383
412
|
- **Resource management:** Configure worker capacity and pending background-work limits, handle queue saturation explicitly, and shut down the process-wide runtime with an observable shutdown report.
|
|
413
|
+
- **Asyncio interoperability:** FlpIt Tasks can be awaited directly inside asyncio coroutines when Task.asyncio_interop is enabled.
|
|
414
|
+
- This is disabled by default.
|
|
415
|
+
- Enabling it adds runtime-detection overhead to every pending await.
|
|
416
|
+
- await in flpit runtime: 200ish nano sec (just loop detection)
|
|
417
|
+
- await in asyncio loop:
|
|
384
418
|
|
|
385
|
-
The runtime starts lazily and manages its own lifecycle.
|
|
419
|
+
The runtime starts lazily and manages its own lifecycle. Cancellation of running synchronous functions is cooperative.
|
|
386
420
|
|
|
387
421
|
See the [Task Runtime examples](https://github.com/riemar/flpit/tree/main/examples/task) for usage examples.
|
|
388
422
|
|
|
@@ -403,25 +437,53 @@ Distributed under the MIT License. See `LICENSE` for more information.
|
|
|
403
437
|
> Note: The test cases exercise both core types, `FlpIt` and `FlpList`, with transitions between lazy and eager execution occurring as part of the individual test cases.
|
|
404
438
|
|
|
405
439
|
```
|
|
406
|
-
|
|
407
|
-
| Test Module File
|
|
408
|
-
|
|
409
|
-
| tests/nettests/
|
|
410
|
-
| ├──
|
|
411
|
-
| ├──
|
|
412
|
-
| ├──
|
|
413
|
-
| ├──
|
|
414
|
-
| ├──
|
|
415
|
-
| ├──
|
|
416
|
-
| ├──
|
|
417
|
-
| ├──
|
|
418
|
-
| ├──
|
|
419
|
-
| ├──
|
|
420
|
-
| ├──
|
|
421
|
-
| ├──
|
|
422
|
-
| ├──
|
|
423
|
-
|
|
|
424
|
-
|
|
425
|
-
|
|
|
426
|
-
|
|
440
|
+
================================================================================
|
|
441
|
+
| Test Module File | Test Cases |
|
|
442
|
+
--------------------------------------------------------------------------------
|
|
443
|
+
| tests/nettests/ | |
|
|
444
|
+
| ├── linq/ | |
|
|
445
|
+
| │ ├── test_aggregate.py | 42 |
|
|
446
|
+
| │ ├── test_all.py | 80 |
|
|
447
|
+
| │ ├── test_any.py | 90 |
|
|
448
|
+
| │ ├── test_average.py | 572 |
|
|
449
|
+
| │ ├── test_chunk.py | 22 |
|
|
450
|
+
| │ ├── test_concat.py | 64 |
|
|
451
|
+
| │ ├── test_contains.py | 108 |
|
|
452
|
+
| │ ├── test_count.py | 37 |
|
|
453
|
+
| │ ├── test_distinct.py | 87 |
|
|
454
|
+
| │ ├── test_element_at.py | 43 |
|
|
455
|
+
| │ ├── test_first.py | 29 |
|
|
456
|
+
| │ ├── test_first_or_default.py | 50 |
|
|
457
|
+
| │ ├── test_full_join.py | 37 |
|
|
458
|
+
| │ ├── test_generic_list.py | 95 |
|
|
459
|
+
| │ ├── test_group_by.py | 23 |
|
|
460
|
+
| │ ├── test_join.py | 47 |
|
|
461
|
+
| │ ├── test_last.py | 29 |
|
|
462
|
+
| │ ├── test_last_or_default.py | 45 |
|
|
463
|
+
| │ ├── test_left_join.py | 47 |
|
|
464
|
+
| │ ├── test_max.py | 1100 |
|
|
465
|
+
| │ ├── test_min.py | 968 |
|
|
466
|
+
| │ ├── test_order_by.py | 100 |
|
|
467
|
+
| │ ├── test_order_descending_by.py | 45 |
|
|
468
|
+
| │ ├── test_reverse.py | 35 |
|
|
469
|
+
| │ ├── test_select.py | 140 |
|
|
470
|
+
| │ ├── test_select_many.py | 85 |
|
|
471
|
+
| │ ├── test_single.py | 34 |
|
|
472
|
+
| │ ├── test_single_or_default.py | 48 |
|
|
473
|
+
| │ ├── test_skip.py | 75 |
|
|
474
|
+
| │ ├── test_sum.py | 134 |
|
|
475
|
+
| │ ├── test_take.py | 39 |
|
|
476
|
+
| │ ├── test_then_by.py | 40 |
|
|
477
|
+
| │ ├── test_then_descending_by.py | 34 |
|
|
478
|
+
| │ ├── test_where.py | 76 |
|
|
479
|
+
| │ └── test_zip.py | 101 |
|
|
480
|
+
| └── task/ | |
|
|
481
|
+
| ├── test_cancellation.py | 69 |
|
|
482
|
+
| ├── test_task_completion_source.py | 26 |
|
|
483
|
+
| ├── test_task_factory.py | 3 |
|
|
484
|
+
| ├── test_task_rt.py | 12 |
|
|
485
|
+
| └── test_task_rt_core.py | 22 |
|
|
486
|
+
--------------------------------------------------------------------------------
|
|
487
|
+
| GRAND TOTALS | 4733 |
|
|
488
|
+
================================================================================
|
|
427
489
|
```
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Fluent LINQ for Python — flip your iterables.
|
|
3
3
|
"""
|
|
4
|
-
from __future__ import annotations
|
|
5
4
|
from importlib.metadata import version
|
|
6
5
|
__version__ = version("flpit")
|
|
7
6
|
|
|
7
|
+
# noinspection protected-member
|
|
8
|
+
from flpit.core._zip import ZipTuple
|
|
9
|
+
from flpit.core._join import JoinTuple
|
|
10
|
+
|
|
8
11
|
from flpit import flp
|
|
9
12
|
from flpit.core.linq import (
|
|
10
13
|
FlpIt,
|
|
11
14
|
FlpList,
|
|
12
|
-
Grouping,
|
|
15
|
+
# Grouping,
|
|
13
16
|
OrderedIt,
|
|
14
17
|
|
|
15
18
|
#====================
|
|
@@ -31,11 +34,18 @@ from flpit.core.linq import (
|
|
|
31
34
|
CollectionNoneError,
|
|
32
35
|
SelectorNoneError,
|
|
33
36
|
SecondNoneError,
|
|
37
|
+
FuncNoneError,
|
|
38
|
+
ResultSelectorNoneError,
|
|
39
|
+
InnerNoneError,
|
|
40
|
+
OuterKeySelectorNoneError,
|
|
41
|
+
InnerKeySelectorNoneError,
|
|
34
42
|
ArgumentOutOfRangeError,
|
|
35
43
|
ArgumentNonNegError,
|
|
36
|
-
ArgumentOutOfBoundsError
|
|
44
|
+
ArgumentOutOfBoundsError,
|
|
37
45
|
)
|
|
38
46
|
|
|
47
|
+
from flpit.core._grouping import Grouping
|
|
48
|
+
|
|
39
49
|
__all__ = [
|
|
40
50
|
"flp",
|
|
41
51
|
"FlpIt",
|
|
@@ -56,8 +66,16 @@ __all__ = [
|
|
|
56
66
|
"CollectionNoneError",
|
|
57
67
|
"SelectorNoneError",
|
|
58
68
|
"SecondNoneError",
|
|
69
|
+
"FuncNoneError",
|
|
70
|
+
"ResultSelectorNoneError",
|
|
71
|
+
"InnerNoneError",
|
|
72
|
+
"OuterKeySelectorNoneError",
|
|
73
|
+
"InnerKeySelectorNoneError",
|
|
59
74
|
"ArgumentOutOfRangeError",
|
|
60
75
|
"ArgumentNonNegError",
|
|
61
76
|
"ArgumentOutOfBoundsError",
|
|
77
|
+
|
|
78
|
+
"ZipTuple",
|
|
79
|
+
"JoinTuple",
|
|
62
80
|
]
|
|
63
81
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from flpit.core.linq import FlpList
|
|
3
|
+
from typing import override
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
|
|
6
|
+
class Grouping[TKey, TItem](FlpList[TItem]):
|
|
7
|
+
"""Represents a collection of elements sharing a common key (.NET IGrouping<TKey, TElement>)."""
|
|
8
|
+
__slots__ = ("_key", )
|
|
9
|
+
|
|
10
|
+
def __init__(self, key: TKey, elements: Iterable[TItem]) -> None:
|
|
11
|
+
self._key: TKey = key
|
|
12
|
+
super().__init__(elements)
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def key(self) -> TKey:
|
|
16
|
+
return self._key
|
|
17
|
+
|
|
18
|
+
@override
|
|
19
|
+
def __repr__(self) -> str:
|
|
20
|
+
return f"Grouping(key={self.key!r}, elements={self.to_list()!r})"
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
def __eq__(self, other: Any) -> bool:
|
|
24
|
+
# Check if the other object is a Grouping (or subclass)
|
|
25
|
+
if not isinstance(other, Grouping):
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
# Compare the keys, then compare the elements inside FlpIt
|
|
29
|
+
return self.key == other.key and self.to_list() == other.to_list()
|