python-iterutils 0.1.10__tar.gz → 0.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-iterutils
3
- Version: 0.1.10
3
+ Version: 0.2
4
4
  Summary: Python another itertools.
5
5
  Home-page: https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils
6
6
  License: MIT
@@ -2,7 +2,7 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __version__ = (0, 1, 10)
5
+ __version__ = (0, 2)
6
6
  __all__ = [
7
7
  "Return", "Yield", "YieldFrom", "iterable", "async_iterable", "foreach", "async_foreach",
8
8
  "through", "async_through", "flatten", "async_flatten", "collect", "async_collect",
@@ -30,7 +30,7 @@ from sys import _getframe
30
30
  from _thread import start_new_thread
31
31
  from time import sleep, time
32
32
  from types import FrameType
33
- from typing import cast, overload, Any, AsyncContextManager, ContextManager, Literal
33
+ from typing import cast, overload, Any, AsyncContextManager, ContextManager, Literal, Protocol
34
34
 
35
35
  from asynctools import (
36
36
  async_filter, async_map, async_reduce, async_zip, async_batched, ensure_async, ensure_aiter,
@@ -38,14 +38,26 @@ from asynctools import (
38
38
  )
39
39
  from decotools import optional
40
40
  from texttools import format_time
41
- from undefined import undefined, Undefined
41
+ from undefined import undefined
42
+
43
+
44
+ class SupportsBool(Protocol):
45
+ def __bool__(self, /) -> bool: ...
46
+
47
+
48
+ class Reraised(BaseException):
49
+
50
+ def __init__(self, exc: BaseException, /):
51
+ if isinstance(exc, Reraised):
52
+ exc = exc.exception
53
+ self.exception: BaseException = exc
42
54
 
43
55
 
44
56
  @dataclass(slots=True, frozen=True)
45
57
  class YieldBase(ABC):
46
58
  value: Any
47
- identity: bool = False
48
- try_call_me: bool = True
59
+ may_await: None | bool = True
60
+ may_call: None | bool = True
49
61
 
50
62
  @property
51
63
  @abstractmethod
@@ -53,12 +65,6 @@ class YieldBase(ABC):
53
65
  ...
54
66
 
55
67
 
56
- class Reraised(BaseException):
57
-
58
- def __init__(self, exc: BaseException, /):
59
- self.exception = exc
60
-
61
-
62
68
  class Return(YieldBase):
63
69
  yield_type = 0
64
70
 
@@ -397,7 +403,7 @@ def filter(
397
403
  def reduce(
398
404
  function: Callable,
399
405
  iterable: Iterable | AsyncIterable,
400
- initial = undefined,
406
+ initial: Any = undefined,
401
407
  /,
402
408
  ):
403
409
  if iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
@@ -646,18 +652,21 @@ def _get_async(back: int = 2) -> bool:
646
652
  @overload
647
653
  def call_as_async[**Args, T: Coroutine](
648
654
  func: Callable[Args, T], /,
655
+ may_await: None | bool = False,
649
656
  threaded: bool = False,
650
657
  ) -> Callable[Args, T]:
651
658
  ...
652
659
  @overload
653
660
  def call_as_async[**Args, T](
654
661
  func: Callable[Args, T], /,
662
+ may_await: None | bool = False,
655
663
  threaded: bool = False,
656
664
  ) -> Callable[Args, Coroutine[Any, Any, T]]:
657
665
  ...
658
666
  def call_as_async[**Args, T](
659
667
  func: Callable[Args, T],
660
668
  /,
669
+ may_await: None | bool = False,
661
670
  threaded: bool = False,
662
671
  ) -> Callable[Args, T] | Callable[Args, Coroutine[Any, Any, T]]:
663
672
  if iscoroutinefunction(func):
@@ -667,144 +676,205 @@ def call_as_async[**Args, T](
667
676
  return func(*args, **kwds)
668
677
  except (StopIteration, StopAsyncIteration) as e:
669
678
  raise Reraised(e) from e
670
- if threaded:
671
- async def wrapper(*args, **kwds):
672
- return await to_thread(wraps, *args, **kwds)
673
- else:
674
- async def wrapper(*args, **kwds):
675
- return wraps(*args, **kwds)
679
+ async def wrapper(*args, **kwds):
680
+ if threaded:
681
+ value = await to_thread(wraps, *args, **kwds)
682
+ else:
683
+ value = wraps(*args, **kwds)
684
+ if may_await is None or may_await and isawaitable(value):
685
+ value = await value
686
+ return value
676
687
  return wrapper
677
688
 
678
689
 
679
690
  def iter_gen_step(
680
691
  gen_step: Generator | Callable[[], Generator],
692
+ simple: bool = False,
681
693
  ):
682
- if callable(gen_step):
683
- gen = gen_step()
684
- else:
685
- gen = gen_step
694
+ gen = gen_step() if callable(gen_step) else gen_step
686
695
  send = gen.send
687
696
  throw = gen.throw
688
697
  close = gen.close
689
- try:
690
- value = send(None)
691
- while True:
692
- if isinstance(value, YieldBase):
693
- raise StopIteration(value)
694
- try:
695
- if callable(value):
696
- value = value()
697
- except BaseException as e:
698
- value = throw(e)
699
- else:
700
- value = send(value)
701
- yield value
702
- except StopIteration as e:
703
- value = e.value
704
- try_call_me = True
705
- if isinstance(value, YieldBase):
706
- try_call_me = value.try_call_me
707
- value = value.value
708
- if callable(value) and try_call_me:
709
- try:
710
- value = value()
711
- except BaseException as e:
698
+ value: Any = None
699
+ if simple:
700
+ try:
701
+ while True:
702
+ yield (value := send(value))
703
+ except StopIteration as e:
704
+ yield e.value
705
+ finally:
706
+ close()
707
+ else:
708
+ try:
709
+ while True:
710
+ if isinstance(value, YieldBase):
711
+ raise StopIteration(value)
712
712
  try:
713
+ if callable(value):
714
+ value = value()
715
+ except BaseException as e:
713
716
  value = throw(e)
717
+ else:
718
+ value = send(value)
719
+ yield value
720
+ except StopIteration as e:
721
+ may_call: None | bool = True
722
+ value = e.value
723
+ if isinstance(value, YieldBase):
724
+ may_call = value.may_call
725
+ value = value.value
726
+ if may_call is None or may_call and callable(value):
727
+ try:
728
+ value = value()
714
729
  except BaseException as e:
715
- raise Reraised(e) from e
716
- yield value
717
- except BaseException as e:
718
- raise Reraised(e) from e
719
- finally:
720
- close()
730
+ try:
731
+ value = throw(e)
732
+ except BaseException as e:
733
+ raise Reraised(e) from e
734
+ yield value
735
+ finally:
736
+ close()
721
737
 
722
738
 
723
739
  async def iter_gen_step_async(
724
740
  gen_step: Generator | Callable[[], Generator],
725
741
  threaded: bool = False,
742
+ simple: bool = False,
726
743
  ):
727
- if callable(gen_step):
728
- gen = gen_step()
729
- else:
730
- gen = gen_step
731
- send: Callable = call_as_async(gen.send, threaded=threaded)
732
- throw: Callable = call_as_async(gen.throw, threaded=threaded)
744
+ gen = gen_step() if callable(gen_step) else gen_step
745
+ send: Callable = gen.send
746
+ throw: Callable = gen.throw
733
747
  close: Callable = call_as_async(gen.close, threaded=threaded)
734
- try:
735
- value = await send(None)
736
- while True:
737
- if isinstance(value, YieldBase):
738
- raise StopIteration(value)
739
- try:
740
- if callable(value):
741
- value = await call_as_async(value, threaded=threaded)()
742
- if isawaitable(value):
743
- value = await value
744
- except BaseException as e:
745
- if isinstance(e, Reraised):
746
- e = e.exception
747
- value = await throw(e)
748
+ value: Any = None
749
+ if simple:
750
+ send = call_as_async(send, may_await=None, threaded=threaded)
751
+ throw = call_as_async(throw, may_await=None, threaded=threaded)
752
+ try:
753
+ while True:
754
+ yield (value := await send(value))
755
+ except BaseException as e:
756
+ if isinstance(e, Reraised):
757
+ e = e.exception
758
+ if isinstance(e, StopIteration):
759
+ yield e.value
748
760
  else:
749
- value = await send(value)
750
- yield value
751
- except BaseException as e:
752
- if isinstance(e, Reraised):
753
- e = e.exception
754
- if isinstance(e, StopIteration):
755
- value = e.value
756
- identity = False
757
- try_call_me = True
758
- if isinstance(value, YieldBase):
759
- identity = value.identity
760
- try_call_me = value.try_call_me
761
- value = value.value
762
- try:
763
- if callable(value) and try_call_me:
764
- value = await call_as_async(value, threaded=threaded)()
765
- if not identity and isawaitable(value):
766
- value = await value
767
- except BaseException as e:
761
+ raise
762
+ finally:
763
+ await close()
764
+ else:
765
+ send = call_as_async(send, threaded=threaded)
766
+ throw = call_as_async(throw, threaded=threaded)
767
+ try:
768
+ while True:
769
+ if isinstance(value, YieldBase):
770
+ raise StopIteration(value)
768
771
  try:
772
+ if callable(value):
773
+ value = await call_as_async(
774
+ value, may_await=True, threaded=threaded)()
775
+ elif isawaitable(value):
776
+ value = await value
777
+ except BaseException as e:
778
+ if isinstance(e, Reraised):
779
+ e = e.exception
769
780
  value = await throw(e)
781
+ else:
782
+ value = await send(value)
783
+ yield value
784
+ except BaseException as e:
785
+ if isinstance(e, Reraised):
786
+ e = e.exception
787
+ if isinstance(e, StopIteration):
788
+ may_await: None | bool = True
789
+ may_call: None | bool = True
790
+ value = e.value
791
+ if isinstance(value, YieldBase):
792
+ may_await = value.may_await
793
+ may_call = value.may_call
794
+ value = value.value
795
+ try:
796
+ if may_call is None or may_call and callable(value):
797
+ value = await call_as_async(
798
+ value, may_await=may_await, threaded=threaded)()
799
+ elif may_await is None or may_await and isawaitable(value):
800
+ value = await value
770
801
  except BaseException as e:
771
- raise Reraised(e) from e
772
- yield value
773
- else:
774
- raise
775
- finally:
776
- await close()
802
+ try:
803
+ value = await throw(e)
804
+ except BaseException as e:
805
+ raise Reraised(e) from e
806
+ yield value
807
+ else:
808
+ raise
809
+ finally:
810
+ await close()
777
811
 
778
812
 
779
- def run_gen_step[T](
813
+ def run_gen_step(
780
814
  gen_step: Generator | Callable[[], Generator],
815
+ simple: bool = False,
816
+ threaded: bool = False,
817
+ running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
781
818
  *,
782
819
  async_: None | bool = None,
783
- threaded: bool = False,
784
820
  ):
785
821
  if async_ is None:
786
822
  async_ = _get_async()
787
823
  if async_:
788
824
  async def process():
825
+ gen = iter_gen_step_async(gen_step, simple=simple, threaded=threaded)
789
826
  try:
790
- async for value in iter_gen_step_async(gen_step, threaded=threaded):
791
- pass
827
+ if running_flag is None:
828
+ async for value in gen:
829
+ pass
830
+ elif callable(running_flag):
831
+ pred = call_as_async(running_flag, threaded=threaded)
832
+ if not await pred():
833
+ raise RuntimeError("stop before starting", gen) from StopIteration()
834
+ async for value in gen:
835
+ if not await pred():
836
+ raise RuntimeError("stop midway", gen) from StopIteration(value)
837
+ else:
838
+ if not running_flag:
839
+ raise RuntimeError("stop before starting", gen) from StopIteration()
840
+ async for value in gen:
841
+ if not running_flag:
842
+ raise RuntimeError("stop midway", gen) from StopIteration(value)
792
843
  return value
793
844
  except Reraised as e:
794
845
  raise e.exception
846
+ except KeyboardInterrupt as e:
847
+ e.args = gen,
848
+ e.add_note(f"stop iterating gen_step: {gen!r}")
849
+ raise
795
850
  return process()
796
851
  else:
852
+ gen = iter_gen_step(gen_step, simple=simple)
797
853
  try:
798
- for value in iter_gen_step(gen_step):
799
- pass
854
+ if running_flag is None:
855
+ for value in gen:
856
+ pass
857
+ else:
858
+ if not callable(running_flag):
859
+ running_flag = running_flag.__bool__
860
+ if not running_flag():
861
+ raise RuntimeError("stop before starting", gen) from StopIteration()
862
+ for value in gen:
863
+ if not running_flag():
864
+ raise RuntimeError("stop midway", gen) from StopIteration(value)
800
865
  return value
801
866
  except Reraised as e:
802
867
  raise e.exception
868
+ except KeyboardInterrupt as e:
869
+ e.args = gen,
870
+ e.add_note(f"stop iterating gen_step: {gen!r}")
871
+ raise
803
872
 
804
873
 
805
874
  @overload
806
875
  def run_gen_step_iter(
807
876
  gen_step: Generator | Callable[[], Generator],
877
+ simple: bool = False,
808
878
  threaded: bool = False,
809
879
  *,
810
880
  async_: None = None,
@@ -813,6 +883,7 @@ def run_gen_step_iter(
813
883
  @overload
814
884
  def run_gen_step_iter(
815
885
  gen_step: Generator | Callable[[], Generator],
886
+ simple: bool = False,
816
887
  threaded: bool = False,
817
888
  *,
818
889
  async_: Literal[False],
@@ -821,6 +892,7 @@ def run_gen_step_iter(
821
892
  @overload
822
893
  def run_gen_step_iter(
823
894
  gen_step: Generator | Callable[[], Generator],
895
+ simple: bool = False,
824
896
  threaded: bool = False,
825
897
  *,
826
898
  async_: Literal[True],
@@ -828,16 +900,14 @@ def run_gen_step_iter(
828
900
  ...
829
901
  def run_gen_step_iter(
830
902
  gen_step: Generator | Callable[[], Generator],
903
+ simple: bool = False,
831
904
  threaded: bool = False,
832
905
  *,
833
906
  async_: None | bool = None,
834
907
  ) -> Iterator | AsyncIterator:
835
908
  if async_ is None:
836
909
  async_ = _get_async()
837
- if callable(gen_step):
838
- gen = gen_step()
839
- else:
840
- gen = gen_step
910
+ gen = gen_step() if callable(gen_step) else gen_step
841
911
  send: Callable = gen.send
842
912
  throw: Callable = gen.throw
843
913
  close: Callable = gen.close
@@ -845,27 +915,30 @@ def run_gen_step_iter(
845
915
  send = call_as_async(send, threaded=threaded)
846
916
  throw = call_as_async(throw, threaded=threaded)
847
917
  close = call_as_async(close, threaded=threaded)
848
- async def process():
849
- async def extract(value):
850
- identity = False
851
- try_call_me = True
852
- yield_type = -1
853
- if isinstance(value, YieldBase):
854
- identity = value.identity
855
- try_call_me = value.try_call_me
856
- yield_type = value.yield_type
857
- value = value.value
858
- if try_call_me and callable(value):
859
- value = await call_as_async(value, threaded=threaded)()
860
- if not identity and isawaitable(value):
861
- value = await value
862
- return yield_type, value
918
+ async def aextract(value, /):
919
+ may_await: None | bool = None if simple else False
920
+ may_call: None | bool = not simple
921
+ yield_type = -1
922
+ if isinstance(value, YieldBase):
923
+ yield_type = value.yield_type
924
+ may_await = value.may_await
925
+ may_call = value.may_call
926
+ value = value.value
927
+ if may_call is None or may_call and callable(value):
928
+ value = await call_as_async(
929
+ value, may_await=may_await, threaded=threaded)()
930
+ elif may_await is None or may_await and isawaitable(value):
931
+ value = await value
932
+ return yield_type, value
933
+ async def aprocess():
863
934
  try:
864
935
  value = await send(None)
865
936
  while True:
866
937
  try:
867
- yield_type, value = await extract(value)
938
+ yield_type, value = await aextract(value)
868
939
  match yield_type:
940
+ case 0:
941
+ return
869
942
  case 1:
870
943
  yield value
871
944
  case 2:
@@ -882,7 +955,7 @@ def run_gen_step_iter(
882
955
  e = e.exception
883
956
  if isinstance(e, StopIteration):
884
957
  try:
885
- yield_type, value = await extract(e.value)
958
+ yield_type, value = await aextract(e.value)
886
959
  match yield_type:
887
960
  case 1:
888
961
  yield value
@@ -897,18 +970,19 @@ def run_gen_step_iter(
897
970
  raise
898
971
  finally:
899
972
  await close()
973
+ return aprocess()
900
974
  else:
975
+ def extract(value, /):
976
+ may_call: None | bool = not simple
977
+ yield_type = -1
978
+ if isinstance(value, YieldBase):
979
+ yield_type = value.yield_type
980
+ may_call = value.may_call
981
+ value = value.value
982
+ if may_call is None or may_call and callable(value):
983
+ value = value()
984
+ return yield_type, value
901
985
  def process():
902
- def extract(value, /):
903
- try_call_me = True
904
- yield_type = -1
905
- if isinstance(value, YieldBase):
906
- try_call_me = value.try_call_me
907
- yield_type = value.yield_type
908
- value = value.value
909
- if try_call_me and callable(value):
910
- value = value()
911
- return yield_type, value
912
986
  try:
913
987
  value = send(None)
914
988
  while True:
@@ -939,23 +1013,38 @@ def run_gen_step_iter(
939
1013
  throw(e)
940
1014
  finally:
941
1015
  close()
942
- return process()
1016
+ return process()
943
1017
 
944
1018
 
945
1019
  @optional
946
1020
  def as_gen_step(
947
1021
  func: Callable,
948
1022
  /,
949
- async_: None | bool = None,
950
1023
  iter: bool = False,
1024
+ simple: bool = False,
1025
+ threaded: bool = False,
1026
+ running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
1027
+ *,
1028
+ async_: None | bool = None,
951
1029
  ) -> Callable:
952
- if async_ is None:
953
- async_ = _get_async()
1030
+ """装饰器,构建一个 gen_step 函数
1031
+ """
954
1032
  def wrapper(*args, **kwds):
955
1033
  if iter:
956
- return run_gen_step_iter(func(*args, **kwds), async_=async_) # type: ignore
1034
+ return run_gen_step_iter(
1035
+ func(*args, **kwds),
1036
+ simple=simple,
1037
+ threaded=threaded,
1038
+ async_=async_, # type: ignore
1039
+ )
957
1040
  else:
958
- return run_gen_step(func(*args, **kwds), async_=async_)
1041
+ return run_gen_step(
1042
+ func(*args, **kwds),
1043
+ simple=simple,
1044
+ threaded=threaded,
1045
+ running_flag=running_flag,
1046
+ async_=async_,
1047
+ )
959
1048
  return wrapper
960
1049
 
961
1050
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-iterutils"
3
- version = "0.1.10"
3
+ version = "0.2"
4
4
  description = "Python another itertools."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"