flpit 0.2.3__tar.gz → 0.2.4__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.3 → flpit-0.2.4}/PKG-INFO +1 -1
- {flpit-0.2.3 → flpit-0.2.4}/pyproject.toml +1 -1
- {flpit-0.2.3 → flpit-0.2.4}/src/flpit/task.py +64 -60
- {flpit-0.2.3 → flpit-0.2.4}/.gitignore +0 -0
- {flpit-0.2.3 → flpit-0.2.4}/LICENSE +0 -0
- {flpit-0.2.3 → flpit-0.2.4}/README.md +0 -0
- {flpit-0.2.3 → flpit-0.2.4}/src/flp/__init__.py +0 -0
- {flpit-0.2.3 → flpit-0.2.4}/src/flpit/__init__.py +0 -0
- {flpit-0.2.3 → flpit-0.2.4}/src/flpit/core/__init__.py +0 -0
- {flpit-0.2.3 → flpit-0.2.4}/src/flpit/core/linq.py +0 -0
- {flpit-0.2.3 → flpit-0.2.4}/src/flpit/csv.py +0 -0
- {flpit-0.2.3 → flpit-0.2.4}/src/flpit/flp.py +0 -0
- {flpit-0.2.3 → flpit-0.2.4}/src/flpit/json.py +0 -0
- {flpit-0.2.3 → flpit-0.2.4}/src/flpit/py.typed +0 -0
|
@@ -49,7 +49,7 @@ from collections.abc import Awaitable, Callable, Coroutine
|
|
|
49
49
|
import flpit
|
|
50
50
|
|
|
51
51
|
T = TypeVar("T")
|
|
52
|
-
_LOG = logging.getLogger("
|
|
52
|
+
_LOG = logging.getLogger("task")
|
|
53
53
|
_MISSING = object()
|
|
54
54
|
_WAKE = object()
|
|
55
55
|
|
|
@@ -71,7 +71,7 @@ class OperationCanceledError(Exception):
|
|
|
71
71
|
super().__init__(message)
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
# Backwards-compatible spelling used by the first
|
|
74
|
+
# Backwards-compatible spelling used by the first Task prototype.
|
|
75
75
|
CancelledError = OperationCanceledError
|
|
76
76
|
|
|
77
77
|
|
|
@@ -236,7 +236,7 @@ class CancellationToken:
|
|
|
236
236
|
|
|
237
237
|
def throw_if_cancellation_requested(self) -> None:
|
|
238
238
|
if self.is_cancellation_requested:
|
|
239
|
-
raise OperationCanceledError("Cancellation was requested")
|
|
239
|
+
raise OperationCanceledError("Cancellation was requested", token=self)
|
|
240
240
|
|
|
241
241
|
def register(self, callback: Callable[[], Any]) -> CancellationTokenRegistration:
|
|
242
242
|
if not callable(callback):
|
|
@@ -393,8 +393,13 @@ class _Operation:
|
|
|
393
393
|
|
|
394
394
|
|
|
395
395
|
class _Sleep(_Operation):
|
|
396
|
-
def __init__(
|
|
397
|
-
|
|
396
|
+
def __init__(
|
|
397
|
+
self,
|
|
398
|
+
runtime: "_Runtime",
|
|
399
|
+
delay: float,
|
|
400
|
+
token: CancellationToken | None = None,
|
|
401
|
+
) -> None:
|
|
402
|
+
super().__init__(runtime, token)
|
|
398
403
|
self.delay = delay
|
|
399
404
|
self.deadline = time.monotonic() + delay
|
|
400
405
|
|
|
@@ -471,8 +476,12 @@ async def _invoke_background(
|
|
|
471
476
|
return result
|
|
472
477
|
|
|
473
478
|
|
|
474
|
-
async def _delay_coroutine(
|
|
475
|
-
|
|
479
|
+
async def _delay_coroutine(
|
|
480
|
+
runtime: "_Runtime",
|
|
481
|
+
delay: float,
|
|
482
|
+
token: CancellationToken | None = None,
|
|
483
|
+
) -> None:
|
|
484
|
+
await _Sleep(runtime, delay, token=token)
|
|
476
485
|
|
|
477
486
|
|
|
478
487
|
class Task(Generic[T]):
|
|
@@ -487,11 +496,10 @@ class Task(Generic[T]):
|
|
|
487
496
|
self,
|
|
488
497
|
runtime: "_Runtime",
|
|
489
498
|
*,
|
|
490
|
-
name: str | None = None,
|
|
491
499
|
token: CancellationToken | None = None,
|
|
492
500
|
) -> None:
|
|
493
501
|
self._runtime = runtime
|
|
494
|
-
self.name =
|
|
502
|
+
self.name = f"Task-{id(self):x}"
|
|
495
503
|
self._state = self.PENDING
|
|
496
504
|
self._result: Any = None
|
|
497
505
|
self._exception: BaseException | None = None
|
|
@@ -560,17 +568,42 @@ class Task(Generic[T]):
|
|
|
560
568
|
raise TypeError("handler must be callable or None")
|
|
561
569
|
_get_runtime().set_exception_handler(handler)
|
|
562
570
|
|
|
571
|
+
@overload
|
|
572
|
+
@classmethod
|
|
573
|
+
def run[R](
|
|
574
|
+
cls,
|
|
575
|
+
work: Callable[..., Coroutine[Any, Any, R]],
|
|
576
|
+
/,
|
|
577
|
+
*args: Any,
|
|
578
|
+
token: CancellationToken | None = None,
|
|
579
|
+
pass_token: bool = False,
|
|
580
|
+
**kwargs: Any,
|
|
581
|
+
) -> Task[R]: ...
|
|
582
|
+
|
|
583
|
+
@overload
|
|
584
|
+
@classmethod
|
|
585
|
+
def run[R](
|
|
586
|
+
cls,
|
|
587
|
+
work: Callable[..., R],
|
|
588
|
+
/,
|
|
589
|
+
*args: Any,
|
|
590
|
+
token: CancellationToken | None = None,
|
|
591
|
+
pass_token: bool = False,
|
|
592
|
+
**kwargs: Any,
|
|
593
|
+
) -> Task[R]: ...
|
|
594
|
+
|
|
563
595
|
@classmethod
|
|
564
596
|
def run(
|
|
565
597
|
cls,
|
|
566
|
-
work: Callable[...,
|
|
598
|
+
work: Callable[..., Any],
|
|
567
599
|
/,
|
|
568
600
|
*args: Any,
|
|
569
|
-
name: str | None = None,
|
|
570
601
|
token: CancellationToken | None = None,
|
|
571
602
|
pass_token: bool = False,
|
|
572
603
|
**kwargs: Any,
|
|
573
|
-
) ->
|
|
604
|
+
) -> Task[Any]:
|
|
605
|
+
# Keep your existing implementation here.
|
|
606
|
+
...
|
|
574
607
|
"""Schedule a coroutine/awaitable or ordinary callable.
|
|
575
608
|
|
|
576
609
|
Ordinary functions run in the bounded worker pool. ``token`` controls
|
|
@@ -594,7 +627,7 @@ class Task(Generic[T]):
|
|
|
594
627
|
work.close()
|
|
595
628
|
raise ValueError("pass_token=True requires an explicit token")
|
|
596
629
|
runtime = _get_runtime()
|
|
597
|
-
task = cls(runtime,
|
|
630
|
+
task = cls(runtime, token=token)
|
|
598
631
|
task._pass_token = pass_token
|
|
599
632
|
runtime._submit(task, work, args, kwargs)
|
|
600
633
|
return task
|
|
@@ -602,9 +635,7 @@ class Task(Generic[T]):
|
|
|
602
635
|
@classmethod
|
|
603
636
|
def when_all(
|
|
604
637
|
cls,
|
|
605
|
-
*tasks:
|
|
606
|
-
name: str | None = None,
|
|
607
|
-
token: CancellationToken | None = None,
|
|
638
|
+
*tasks: Awaitable[T]
|
|
608
639
|
) -> "Task[list[Any]]":
|
|
609
640
|
"""Complete when every supplied task completes, preserving input order.
|
|
610
641
|
|
|
@@ -615,22 +646,16 @@ class Task(Generic[T]):
|
|
|
615
646
|
several children fail, TaskAggregateError exposes all their exceptions.
|
|
616
647
|
If there are no tasks, the result is an empty list.
|
|
617
648
|
"""
|
|
618
|
-
if token is not None and not isinstance(token, CancellationToken):
|
|
619
|
-
raise TypeError("token must be a CancellationToken")
|
|
620
649
|
normalized = _normalize_tasks(tasks, allow_empty=True)
|
|
621
650
|
return cast(
|
|
622
651
|
"Task[list[Any]]",
|
|
623
|
-
|
|
624
|
-
# pyrefly: ignore [bad-argument-type]
|
|
625
|
-
cls.run(_when_all_coroutine, normalized, name=name, token=token),
|
|
652
|
+
cls.run(_when_all_coroutine, normalized),
|
|
626
653
|
)
|
|
627
654
|
|
|
628
655
|
@classmethod
|
|
629
656
|
def when_any(
|
|
630
657
|
cls,
|
|
631
658
|
*tasks: Any,
|
|
632
|
-
name: str | None = None,
|
|
633
|
-
token: CancellationToken | None = None,
|
|
634
659
|
) -> "Task[Task[Any]]":
|
|
635
660
|
"""Complete when one supplied task completes and return that task.
|
|
636
661
|
|
|
@@ -642,14 +667,10 @@ class Task(Generic[T]):
|
|
|
642
667
|
If multiple tasks are already complete at observation time, input order
|
|
643
668
|
breaks the tie deterministically.
|
|
644
669
|
"""
|
|
645
|
-
if token is not None and not isinstance(token, CancellationToken):
|
|
646
|
-
raise TypeError("token must be a CancellationToken")
|
|
647
670
|
normalized = _normalize_tasks(tasks, allow_empty=False)
|
|
648
671
|
return cast(
|
|
649
672
|
"Task[Task[Any]]",
|
|
650
|
-
|
|
651
|
-
# pyrefly: ignore [bad-argument-type]
|
|
652
|
-
cls.run(_when_any_coroutine, normalized, name=name, token=token),
|
|
673
|
+
cls.run(_when_any_coroutine, normalized),
|
|
653
674
|
)
|
|
654
675
|
|
|
655
676
|
@staticmethod
|
|
@@ -674,7 +695,6 @@ class Task(Generic[T]):
|
|
|
674
695
|
seconds: float,
|
|
675
696
|
*,
|
|
676
697
|
token: CancellationToken | None = None,
|
|
677
|
-
name: str | None = None,
|
|
678
698
|
) -> "Task[None]":
|
|
679
699
|
"""Return a task completed after seconds without blocking a worker."""
|
|
680
700
|
if seconds < 0:
|
|
@@ -682,12 +702,12 @@ class Task(Generic[T]):
|
|
|
682
702
|
if token is not None and not isinstance(token, CancellationToken):
|
|
683
703
|
raise TypeError("token must be a CancellationToken")
|
|
684
704
|
runtime = _get_runtime()
|
|
685
|
-
task: Task[None] = cls(runtime,
|
|
705
|
+
task: Task[None] = cls(runtime, token=token)
|
|
686
706
|
# During graceful cancellation, allow an already-running coroutine to
|
|
687
707
|
# create a delay for async cleanup. External callers are still rejected.
|
|
688
708
|
runtime._submit_coroutine(
|
|
689
709
|
task,
|
|
690
|
-
_delay_coroutine(runtime, seconds),
|
|
710
|
+
_delay_coroutine(runtime, seconds, token),
|
|
691
711
|
allow_during_shutdown=runtime.is_runtime_thread(),
|
|
692
712
|
)
|
|
693
713
|
return task
|
|
@@ -754,16 +774,6 @@ class Task(Generic[T]):
|
|
|
754
774
|
return self._exception
|
|
755
775
|
return OperationCanceledError(f"{self.name} was cancelled")
|
|
756
776
|
|
|
757
|
-
def cancel(self) -> bool:
|
|
758
|
-
"""Request cancellation of this Task, independently of other token users."""
|
|
759
|
-
if self.done():
|
|
760
|
-
return False
|
|
761
|
-
try:
|
|
762
|
-
self._runtime._post(lambda: self._cancel_on_runtime(), internal=True)
|
|
763
|
-
except RuntimeShutdownError:
|
|
764
|
-
return False
|
|
765
|
-
return True
|
|
766
|
-
|
|
767
777
|
def __await__(self) -> Generator[Any, T, T]:
|
|
768
778
|
if self.done():
|
|
769
779
|
return self._raise_or_return()
|
|
@@ -780,11 +790,6 @@ class Task(Generic[T]):
|
|
|
780
790
|
raise InvalidStateError(f"{self.name} has not completed")
|
|
781
791
|
return cast(T, self._result)
|
|
782
792
|
|
|
783
|
-
def _bind_token(self) -> None:
|
|
784
|
-
if not self._token.can_be_cancelled or self._token_registration is not None:
|
|
785
|
-
return
|
|
786
|
-
self._token_registration = self._token.register(self.cancel)
|
|
787
|
-
|
|
788
793
|
def _cancel_on_runtime(self) -> None:
|
|
789
794
|
if self.done():
|
|
790
795
|
return
|
|
@@ -1152,7 +1157,7 @@ class _Runtime:
|
|
|
1152
1157
|
self._max_pending_background = max_pending_background
|
|
1153
1158
|
self._pending_background: deque[tuple[Task[Any], _BackgroundCall]] = deque()
|
|
1154
1159
|
self._background_active = 0 # scheduler-thread-owned submitted call count
|
|
1155
|
-
self._executor = ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="
|
|
1160
|
+
self._executor = ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="Thread")
|
|
1156
1161
|
self._inflight_lock = threading.Condition()
|
|
1157
1162
|
self._inflight_futures: set[Future[Any]] = set()
|
|
1158
1163
|
self._current_task: Task[Any] | None = None
|
|
@@ -1181,8 +1186,8 @@ class _Runtime:
|
|
|
1181
1186
|
return
|
|
1182
1187
|
with self._state_lock:
|
|
1183
1188
|
if self._closed or self._shutdown_requested:
|
|
1184
|
-
raise RuntimeShutdownError("
|
|
1185
|
-
self._thread = threading.Thread(target=self._loop, name="
|
|
1189
|
+
raise RuntimeShutdownError("Task runtime is shutting down")
|
|
1190
|
+
self._thread = threading.Thread(target=self._loop, name="Task-runtime", daemon=True)
|
|
1186
1191
|
self._thread.start()
|
|
1187
1192
|
self._started.wait()
|
|
1188
1193
|
|
|
@@ -1199,8 +1204,8 @@ class _Runtime:
|
|
|
1199
1204
|
handler({**context, "exception": error})
|
|
1200
1205
|
return
|
|
1201
1206
|
except Exception:
|
|
1202
|
-
_LOG.exception("
|
|
1203
|
-
message = str(context.get("message", "Unexpected
|
|
1207
|
+
_LOG.exception("Task exception handler itself failed")
|
|
1208
|
+
message = str(context.get("message", "Unexpected Task runtime error"))
|
|
1204
1209
|
_LOG.error("%s", message, exc_info=(type(error), error, error.__traceback__))
|
|
1205
1210
|
|
|
1206
1211
|
def _call_sync(self, callback: Callable[[], T], *, internal: bool = False) -> T:
|
|
@@ -1230,7 +1235,7 @@ class _Runtime:
|
|
|
1230
1235
|
thread = self._thread
|
|
1231
1236
|
if thread is None or not thread.is_alive():
|
|
1232
1237
|
raise RuntimeShutdownError(
|
|
1233
|
-
"
|
|
1238
|
+
"Task runtime stopped before the scheduled operation ran"
|
|
1234
1239
|
)
|
|
1235
1240
|
if "error" in outcome:
|
|
1236
1241
|
raise outcome["error"]
|
|
@@ -1241,7 +1246,7 @@ class _Runtime:
|
|
|
1241
1246
|
execute_inline = False
|
|
1242
1247
|
with self._state_lock:
|
|
1243
1248
|
if self._closed or self._stopping or (self._shutdown_requested and not internal):
|
|
1244
|
-
raise RuntimeShutdownError("
|
|
1249
|
+
raise RuntimeShutdownError("Task runtime is shutting down")
|
|
1245
1250
|
if self.is_runtime_thread():
|
|
1246
1251
|
execute_inline = True
|
|
1247
1252
|
else:
|
|
@@ -1312,7 +1317,6 @@ class _Runtime:
|
|
|
1312
1317
|
return
|
|
1313
1318
|
task._coroutine = coroutine
|
|
1314
1319
|
self._tasks.add(task)
|
|
1315
|
-
task._bind_token()
|
|
1316
1320
|
self._schedule(task)
|
|
1317
1321
|
|
|
1318
1322
|
def _schedule(
|
|
@@ -1361,7 +1365,7 @@ class _Runtime:
|
|
|
1361
1365
|
if not isinstance(yielded, _Operation):
|
|
1362
1366
|
raise RuntimeError(
|
|
1363
1367
|
f"{task.name} yielded unsupported object {yielded!r}; "
|
|
1364
|
-
"this runtime only supports
|
|
1368
|
+
"this runtime only supports Task awaitables"
|
|
1365
1369
|
)
|
|
1366
1370
|
if yielded.runtime is not self:
|
|
1367
1371
|
raise RuntimeError("awaitable belongs to a different runtime")
|
|
@@ -1614,7 +1618,7 @@ class _Runtime:
|
|
|
1614
1618
|
callback()
|
|
1615
1619
|
except BaseException as error:
|
|
1616
1620
|
self._report_exception(
|
|
1617
|
-
{"message": "Unhandled exception in
|
|
1621
|
+
{"message": "Unhandled exception in Task scheduler callback", "callback": callback},
|
|
1618
1622
|
error,
|
|
1619
1623
|
)
|
|
1620
1624
|
|
|
@@ -1704,7 +1708,7 @@ class _Runtime:
|
|
|
1704
1708
|
if self._stopping or self.closed:
|
|
1705
1709
|
break
|
|
1706
1710
|
self._report_exception(
|
|
1707
|
-
{"message": "Selector failed; stopping
|
|
1711
|
+
{"message": "Selector failed; stopping Task runtime"}, error
|
|
1708
1712
|
)
|
|
1709
1713
|
self._stopping = True
|
|
1710
1714
|
break
|
|
@@ -1716,7 +1720,7 @@ class _Runtime:
|
|
|
1716
1720
|
)
|
|
1717
1721
|
self._stopping = True
|
|
1718
1722
|
except BaseException as error:
|
|
1719
|
-
self._report_exception({"message": "Fatal error in
|
|
1723
|
+
self._report_exception({"message": "Fatal error in Task scheduler loop"}, error)
|
|
1720
1724
|
self._stopping = True
|
|
1721
1725
|
finally:
|
|
1722
1726
|
# Stop accepting work before releasing scheduler resources. The
|
|
@@ -1771,7 +1775,7 @@ class _Runtime:
|
|
|
1771
1775
|
runtime_stopped = thread is None or not thread.is_alive()
|
|
1772
1776
|
if not runtime_stopped:
|
|
1773
1777
|
self._report_exception(
|
|
1774
|
-
{"message": "
|
|
1778
|
+
{"message": "Task scheduler thread did not stop before timeout"},
|
|
1775
1779
|
TimeoutError("scheduler shutdown timed out"),
|
|
1776
1780
|
)
|
|
1777
1781
|
|
|
@@ -1825,7 +1829,7 @@ def _shutdown_at_exit() -> None:
|
|
|
1825
1829
|
try:
|
|
1826
1830
|
Task.shutdown(grace_timeout=0.2, worker_timeout=0.2)
|
|
1827
1831
|
except BaseException:
|
|
1828
|
-
_LOG.exception("Error during
|
|
1832
|
+
_LOG.exception("Error during Task interpreter shutdown")
|
|
1829
1833
|
|
|
1830
1834
|
|
|
1831
1835
|
atexit.register(_shutdown_at_exit)
|
|
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
|