develia-pytools 0.1.0__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.
- develia_pytools-0.1.0/PKG-INFO +17 -0
- develia_pytools-0.1.0/pyproject.toml +16 -0
- develia_pytools-0.1.0/pytools/__init__.py +7 -0
- develia_pytools-0.1.0/pytools/_functions.py +172 -0
- develia_pytools-0.1.0/pytools/_lang.py +10 -0
- develia_pytools-0.1.0/pytools/_lazy.py +32 -0
- develia_pytools-0.1.0/pytools/_range.py +69 -0
- develia_pytools-0.1.0/pytools/_result.py +16 -0
- develia_pytools-0.1.0/pytools/_segment.py +59 -0
- develia_pytools-0.1.0/pytools/algorithms.py +39 -0
- develia_pytools-0.1.0/pytools/asynctools.py +52 -0
- develia_pytools-0.1.0/pytools/collections/__init__.py +2 -0
- develia_pytools-0.1.0/pytools/collections/_lazy_sequence.py +38 -0
- develia_pytools-0.1.0/pytools/collections/_stream.py +899 -0
- develia_pytools-0.1.0/pytools/collections2.py +91 -0
- develia_pytools-0.1.0/pytools/console.py +28 -0
- develia_pytools-0.1.0/pytools/datetools.py +91 -0
- develia_pytools-0.1.0/pytools/ftp.py +102 -0
- develia_pytools-0.1.0/pytools/functions.py +2 -0
- develia_pytools-0.1.0/pytools/importer.py +34 -0
- develia_pytools-0.1.0/pytools/io.py +300 -0
- develia_pytools-0.1.0/pytools/iteratools.py +583 -0
- develia_pytools-0.1.0/pytools/math.py +138 -0
- develia_pytools-0.1.0/pytools/parser.py +36 -0
- develia_pytools-0.1.0/pytools/sequence.py +16 -0
- develia_pytools-0.1.0/pytools/serializer.py +25 -0
- develia_pytools-0.1.0/pytools/strings.py +38 -0
- develia_pytools-0.1.0/pytools/timetools.py +140 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: develia-pytools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: General utility library.
|
|
5
|
+
Author: Antonio Gil Espinosa
|
|
6
|
+
Author-email: antonio.gil.espinosa@gmail.com
|
|
7
|
+
Requires-Python: >=3.6,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "develia-pytools"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "General utility library."
|
|
5
|
+
authors = ["Antonio Gil Espinosa <antonio.gil.espinosa@gmail.com>"]
|
|
6
|
+
#readme = "README.md"
|
|
7
|
+
packages = [
|
|
8
|
+
{ include = "pytools" }
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[tool.poetry.dependencies]
|
|
12
|
+
python = "^3.6"
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["poetry-core"]
|
|
16
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import typing as _t
|
|
2
|
+
|
|
3
|
+
_T = _t.TypeVar('_T')
|
|
4
|
+
_R = _t.TypeVar("_R")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def staticinitialization(cls: _t.Type):
|
|
8
|
+
"""
|
|
9
|
+
Perform static initialization for a given class.
|
|
10
|
+
|
|
11
|
+
:param cls: The class to perform static initialization on.
|
|
12
|
+
:return: The modified class with static initialization.
|
|
13
|
+
:raises Exception: If `cls.__static_init__` is not callable.
|
|
14
|
+
"""
|
|
15
|
+
if hasattr(cls, "__static_init__") and callable(cls.__static_init__):
|
|
16
|
+
|
|
17
|
+
cls.__static_init__()
|
|
18
|
+
|
|
19
|
+
def raise_exception(*args, **kwargs):
|
|
20
|
+
raise Exception("The static initializer cannot be called more than once.")
|
|
21
|
+
|
|
22
|
+
cls.__static_init__ = raise_exception
|
|
23
|
+
|
|
24
|
+
else:
|
|
25
|
+
raise Exception(f"Method \'{cls.__name__}__static_init__\' is not implemented")
|
|
26
|
+
|
|
27
|
+
return cls
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def new(cls: _t.Type[_T], *args, **kwargs) -> _T:
|
|
31
|
+
"""
|
|
32
|
+
Create a new instance of a class.
|
|
33
|
+
|
|
34
|
+
:param cls: The class to create an instance of.
|
|
35
|
+
:param args: Positional arguments that will be passed to the class constructor.
|
|
36
|
+
:param kwargs: Keyword arguments that will be passed to the class constructor.
|
|
37
|
+
:return: The newly created instance of the class.
|
|
38
|
+
"""
|
|
39
|
+
return cls.__new__(cls, *args, **kwargs)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def clamp(x, min, max):
|
|
43
|
+
"""
|
|
44
|
+
Clamp a value to a specified range.
|
|
45
|
+
|
|
46
|
+
:param x: The value to be clamped.
|
|
47
|
+
:param min: The minimum value of the range.
|
|
48
|
+
:param max: The maximum value of the range.
|
|
49
|
+
:return: The clamped value.
|
|
50
|
+
"""
|
|
51
|
+
if x < min:
|
|
52
|
+
return min
|
|
53
|
+
if x > max:
|
|
54
|
+
return max
|
|
55
|
+
return x
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def compare(a: _t.Any, b: _t.Any) -> int:
|
|
59
|
+
"""
|
|
60
|
+
:param a: The first value to compare.
|
|
61
|
+
:param b: The second value to compare.
|
|
62
|
+
:return: -1 if `a` is less than `b`, 1 if `a` is greater than `b`, 0 if `a` is equal to `b`.
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
if a < b:
|
|
66
|
+
return -1
|
|
67
|
+
elif a > b:
|
|
68
|
+
return 1
|
|
69
|
+
else:
|
|
70
|
+
return 0
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def unzip(iterable: _t.Iterable) -> _t.Iterable:
|
|
74
|
+
"""
|
|
75
|
+
Unzips an iterable by transposing the elements of the nested iterables.
|
|
76
|
+
|
|
77
|
+
:param iterable: An iterable containing nested iterables.
|
|
78
|
+
:return: An iterable of tuples, each representing a column of the transposed elements.
|
|
79
|
+
"""
|
|
80
|
+
return zip(*iterable)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def try_resolve(obj: _t.Any, key: str, fallback=None):
|
|
84
|
+
"""
|
|
85
|
+
Tries to resolve the value of a given key in an object and returns a Result.
|
|
86
|
+
|
|
87
|
+
:param obj: The object to resolve the key from.
|
|
88
|
+
:param key: The key to resolve in the object.
|
|
89
|
+
:param fallback: The fallback value to return if the key is not found. Default is None.
|
|
90
|
+
:return: A Result object containing a boolean indicating if the key was found,
|
|
91
|
+
and the value of the key if found or the fallback value if not found.
|
|
92
|
+
"""
|
|
93
|
+
from pytools import Result
|
|
94
|
+
|
|
95
|
+
if hasattr(obj, key):
|
|
96
|
+
return Result(True, getattr(obj, key))
|
|
97
|
+
|
|
98
|
+
return Result(False, fallback)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def debugging() -> bool:
|
|
102
|
+
"""
|
|
103
|
+
Check if the Python interpreter is currently being run in a debugger.
|
|
104
|
+
|
|
105
|
+
:return: Returns True if the interpreter is being run in a debugger, False otherwise.
|
|
106
|
+
:rtype: bool
|
|
107
|
+
"""
|
|
108
|
+
import sys
|
|
109
|
+
output = hasattr(sys, 'gettrace') and sys.gettrace() is not None
|
|
110
|
+
print(output)
|
|
111
|
+
return output
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def between(value, min, max, inclusive=True):
|
|
115
|
+
"""
|
|
116
|
+
:param value: The value to check if it is between the minimum and maximum values.
|
|
117
|
+
:param min: The minimum value of the range.
|
|
118
|
+
:param max: The maximum value of the range.
|
|
119
|
+
:param inclusive: Optional parameter indicating if the minimum and maximum values should be inclusive (default is True).
|
|
120
|
+
:return: True if the value is between the minimum and maximum values (inclusive or exclusive based on the inclusive parameter), False otherwise.
|
|
121
|
+
"""
|
|
122
|
+
if inclusive:
|
|
123
|
+
return min <= value <= max
|
|
124
|
+
else:
|
|
125
|
+
return min < value < max
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def evaluate(obj: _t.Union[_t.Callable[[], _T], _T]) -> _T:
|
|
129
|
+
"""
|
|
130
|
+
:param obj: The object to be evaluated. It can either be a callable or a regular object.
|
|
131
|
+
:return: The result of evaluating the object. If the object is callable, the returned value is the result of calling the object.
|
|
132
|
+
If the object is not callable, the returned value is the object itself.
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
if callable(obj):
|
|
136
|
+
return obj()
|
|
137
|
+
return obj
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def tap(obj: _T, callable: _t.Callable[[_T], _t.Any]) -> _T:
|
|
141
|
+
"""
|
|
142
|
+
Apply a Callable function to an object and return the object.
|
|
143
|
+
|
|
144
|
+
:param obj: The object to apply the callable function to.
|
|
145
|
+
:param callable: The Callable function that will be applied to the object.
|
|
146
|
+
:return: The object after applying the callable function.
|
|
147
|
+
"""
|
|
148
|
+
callable(obj)
|
|
149
|
+
return obj
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def coalesce(arg1, arg2) -> _t.Optional[_T]:
|
|
153
|
+
return arg1 if arg1 is not None else arg2
|
|
154
|
+
|
|
155
|
+
# def parse(target_type: _typing.Union[_typing.Type[int], _typing.Type[float]], string: str,
|
|
156
|
+
# decimal_separator: str = ".",
|
|
157
|
+
# thousands_separator: str = None,
|
|
158
|
+
# fallback: _typing.Any = None) -> _typing.Any:
|
|
159
|
+
# if thousands_separator is not None:
|
|
160
|
+
# string = string.replace(thousands_separator, "")
|
|
161
|
+
#
|
|
162
|
+
# try:
|
|
163
|
+
# if target_type == float:
|
|
164
|
+
# if decimal_separator != ".":
|
|
165
|
+
# string = string.replace(decimal_separator, ".")
|
|
166
|
+
# return float(string)
|
|
167
|
+
# elif target_type == int:
|
|
168
|
+
# return int(string)
|
|
169
|
+
#
|
|
170
|
+
# return fallback() if callable(fallback) else fallback
|
|
171
|
+
# except ValueError:
|
|
172
|
+
# return fallback() if callable(fallback) else fallback
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import typing as _t
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class segment:
|
|
5
|
+
|
|
6
|
+
def __new__(cls, start: _t.Optional[int] = None, count: _t.Optional[int] = None, step: _t.Optional[int] = None) -> slice:
|
|
7
|
+
return slice(start, (start + count) if (count is not None and start is not None) else start, step)
|
|
8
|
+
|
|
9
|
+
def __class_getitem__(cls, item: slice) -> slice:
|
|
10
|
+
return slice(item.start, (item.start + item.stop) if (item.stop is not None and item.start is not None) else item.start, item.step)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import threading as _threading
|
|
2
|
+
import typing as _t
|
|
3
|
+
|
|
4
|
+
_T = _t.TypeVar("_T")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Lazy(_t.Generic[_T]):
|
|
8
|
+
def __init__(self, factory_method: _t.Callable[[], _T], lock=None):
|
|
9
|
+
self._factory_method = factory_method
|
|
10
|
+
self._value_created = False
|
|
11
|
+
self._value = None
|
|
12
|
+
self._lock = lock if lock is not None else _threading.RLock()
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def value_created(self) -> bool:
|
|
16
|
+
return self._value_created
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def value(self) -> _T:
|
|
20
|
+
with self._lock:
|
|
21
|
+
if not self._value_created:
|
|
22
|
+
self._value = self._factory_method()
|
|
23
|
+
self._value_created = True
|
|
24
|
+
|
|
25
|
+
return self._value
|
|
26
|
+
|
|
27
|
+
def __repr__(self): # pragma: no cover
|
|
28
|
+
with self._lock:
|
|
29
|
+
if self._value_created:
|
|
30
|
+
return repr(self._value)
|
|
31
|
+
|
|
32
|
+
return "Value not created yet."
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import typing as _t
|
|
2
|
+
|
|
3
|
+
_T = _t.TypeVar("_T")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Range(_t.Generic[_T]):
|
|
7
|
+
def __init__(self, start: _T, end: _T):
|
|
8
|
+
self.end = end
|
|
9
|
+
self.start = start
|
|
10
|
+
|
|
11
|
+
def contains(self, item: _T, start_inclusive: bool = True, end_inclusive: bool = True) -> bool:
|
|
12
|
+
if isinstance(item, Range):
|
|
13
|
+
return self.contains(item.start, start_inclusive, end_inclusive) and self.contains(item.end, start_inclusive, end_inclusive)
|
|
14
|
+
else:
|
|
15
|
+
return (((item >= self.start) if start_inclusive else (item > self.start)) and
|
|
16
|
+
((item <= self.end) if end_inclusive else (item < self.end)))
|
|
17
|
+
|
|
18
|
+
def overlaps(self, start: _T, end: _T, inclusive: bool = True) -> bool:
|
|
19
|
+
return self.overlaps_range(Range(start, end), inclusive)
|
|
20
|
+
|
|
21
|
+
def overlaps_range(self, range: "Range[_T]", inclusive: bool = True) -> bool:
|
|
22
|
+
return self.contains(range.start, inclusive) or self.contains(range.end, inclusive) or \
|
|
23
|
+
range.contains(self.start, inclusive) or range.contains(self.end, inclusive)
|
|
24
|
+
|
|
25
|
+
def split(self, size) -> _t.List[_T]:
|
|
26
|
+
|
|
27
|
+
output = []
|
|
28
|
+
|
|
29
|
+
interval = Range(self.start, min(self.start + size, self.end))
|
|
30
|
+
|
|
31
|
+
while self.contains(interval):
|
|
32
|
+
output.append(interval)
|
|
33
|
+
interval = Range(interval.start + size, min(interval.end + size, self.end))
|
|
34
|
+
|
|
35
|
+
return output
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
return str(self.start) + " - " + str(self.end)
|
|
39
|
+
|
|
40
|
+
def intersect(self, other: 'Range[_T]') -> _t.Optional['Range[_T]']:
|
|
41
|
+
# Lógica para la intersección de intervalos
|
|
42
|
+
start = max(self.start, other.start)
|
|
43
|
+
end = min(self.end, other.end)
|
|
44
|
+
if start < end:
|
|
45
|
+
return Range(start, end)
|
|
46
|
+
else:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
def subtract(self, other):
|
|
50
|
+
if self.start >= other.end or self.end <= other.start:
|
|
51
|
+
# No hay intersección
|
|
52
|
+
return [Range(self.start, self.start)]
|
|
53
|
+
elif other.start <= self.start and other.end >= self.end:
|
|
54
|
+
# B cubre completamente A
|
|
55
|
+
return []
|
|
56
|
+
else:
|
|
57
|
+
# Intersección parcial
|
|
58
|
+
result = []
|
|
59
|
+
if self.start < other.start:
|
|
60
|
+
result.append(Range(self.start, other.start))
|
|
61
|
+
if self.end > other.end:
|
|
62
|
+
result.append(Range(other.end, self.end))
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
def __and__(self, other) -> _t.Optional['Range[_T]']:
|
|
66
|
+
return self.intersect(other)
|
|
67
|
+
|
|
68
|
+
def __sub__(self, other) -> _t.List["Range[_T]"]:
|
|
69
|
+
return self.subtract(other)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import typing as _t
|
|
2
|
+
|
|
3
|
+
_T = _t.TypeVar("_T")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Result(_t.Generic[_T]):
|
|
7
|
+
def __init__(self, success: bool, data: _T):
|
|
8
|
+
self._data: _T = data
|
|
9
|
+
self._success: bool = success
|
|
10
|
+
|
|
11
|
+
def __bool__(self):
|
|
12
|
+
return self._success
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def value(self):
|
|
16
|
+
return self._data
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
from functools import wraps
|
|
3
|
+
import typing as _t
|
|
4
|
+
|
|
5
|
+
_sequenciable = _t.Union[_t.Sequence, 'Segment', typing.List, typing.Tuple]
|
|
6
|
+
|
|
7
|
+
_T = typing.TypeVar('_T')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Segment(_t.Generic[_T]):
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def stop(self):
|
|
14
|
+
return self._stop
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def start(self):
|
|
18
|
+
return self._start
|
|
19
|
+
|
|
20
|
+
def __init__(self, sequence: _sequenciable, start: int, stop: int = None, count: int = None):
|
|
21
|
+
|
|
22
|
+
assert not (stop is not None and count is not None)
|
|
23
|
+
|
|
24
|
+
if count is not None:
|
|
25
|
+
self._stop = start + count
|
|
26
|
+
assert self._stop <= len(sequence)
|
|
27
|
+
elif stop is not None:
|
|
28
|
+
self._stop = stop
|
|
29
|
+
else:
|
|
30
|
+
self._stop = len(sequence)
|
|
31
|
+
|
|
32
|
+
self._start = start
|
|
33
|
+
self._sequence: _sequenciable = sequence
|
|
34
|
+
|
|
35
|
+
def __getitem__(self, item) -> _T:
|
|
36
|
+
# if isinstance(item, slice):
|
|
37
|
+
# start = item.start + self._start
|
|
38
|
+
# stop = start + item.stop
|
|
39
|
+
# return self._sequence[start:stop: item.step]
|
|
40
|
+
|
|
41
|
+
return self._sequence[item]
|
|
42
|
+
|
|
43
|
+
def __setitem__(self, item: int, value: _T):
|
|
44
|
+
# if isinstance(item, slice):
|
|
45
|
+
# start = item.start + self._start
|
|
46
|
+
# stop = start + item.stop
|
|
47
|
+
# self._sequence[start:stop: item.step] = value
|
|
48
|
+
|
|
49
|
+
self._sequence[item + self._start] = value
|
|
50
|
+
|
|
51
|
+
def __iter__(self) -> _t.Iterator[_T]:
|
|
52
|
+
for x in range(self._start, self._stop):
|
|
53
|
+
yield self._sequence[x]
|
|
54
|
+
|
|
55
|
+
def __len__(self):
|
|
56
|
+
return self._stop - self._start
|
|
57
|
+
|
|
58
|
+
# def __repr__(self):
|
|
59
|
+
# return self._sequence[self._start:self._stop].__repr__()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import typing as _t
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def binary_search(start_idx: int, end_idx: int, getter: _t.Callable[[int], int]) -> int:
|
|
5
|
+
"""
|
|
6
|
+
Perform binary search to find a target element within a given range.
|
|
7
|
+
|
|
8
|
+
:param start_idx: The starting index of the range to search within (inclusive).
|
|
9
|
+
:param end_idx: The ending index of the range to search within (inclusive).
|
|
10
|
+
:param getter: A function that takes an index and returns the corresponding element for comparison.
|
|
11
|
+
|
|
12
|
+
:return: The index of the target element within the range, or the index where the target element should be inserted if not found.
|
|
13
|
+
|
|
14
|
+
Example usage:
|
|
15
|
+
```
|
|
16
|
+
def getter(index):
|
|
17
|
+
# Assume array is a sorted list of integers
|
|
18
|
+
return array[index]
|
|
19
|
+
|
|
20
|
+
target_index = binary_search(0, len(array) - 1, getter)
|
|
21
|
+
```
|
|
22
|
+
"""
|
|
23
|
+
start = start_idx
|
|
24
|
+
end = end_idx
|
|
25
|
+
|
|
26
|
+
while start != end:
|
|
27
|
+
|
|
28
|
+
mid = int((start + end) / 2)
|
|
29
|
+
comparison = getter(mid)
|
|
30
|
+
|
|
31
|
+
if comparison == 0:
|
|
32
|
+
return mid
|
|
33
|
+
|
|
34
|
+
if comparison < 0:
|
|
35
|
+
end = mid - 1
|
|
36
|
+
else:
|
|
37
|
+
start = mid + 1
|
|
38
|
+
|
|
39
|
+
return start
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import asyncio as _asyncio
|
|
2
|
+
import typing as _t
|
|
3
|
+
from concurrent.futures import Future as _Future
|
|
4
|
+
from enum import Enum as _Enum
|
|
5
|
+
from multiprocessing import Process as _Process
|
|
6
|
+
from threading import Thread as _Thread
|
|
7
|
+
|
|
8
|
+
_T = _t.TypeVar('_T')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncType(_Enum):
|
|
12
|
+
THREADING = "threading"
|
|
13
|
+
MULTIPROCESSING = "multiprocessing"
|
|
14
|
+
ASYNCIO = "asyncio"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def wait_all(tasks: _t.Optional[_t.Iterable] = None):
|
|
18
|
+
tasks = [] if tasks is None else tasks
|
|
19
|
+
for task in tasks:
|
|
20
|
+
if isinstance(task, _Thread) or isinstance(task, _Process):
|
|
21
|
+
task.join()
|
|
22
|
+
elif isinstance(task, _asyncio.Task):
|
|
23
|
+
_asyncio.get_running_loop().run_until_complete(task)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class EndSignal:
|
|
27
|
+
def __new__(cls, *args, **kwargs):
|
|
28
|
+
return cls
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run(func: _t.Callable[[], _T],
|
|
32
|
+
async_type: _t.Optional[_t.Union[AsyncType, str]] = AsyncType.THREADING,
|
|
33
|
+
*args,
|
|
34
|
+
**kwarg) -> _Future[_T]:
|
|
35
|
+
future = _Future()
|
|
36
|
+
|
|
37
|
+
def job():
|
|
38
|
+
try:
|
|
39
|
+
future.set_result(func())
|
|
40
|
+
except Exception as ex:
|
|
41
|
+
future.set_exception(ex)
|
|
42
|
+
|
|
43
|
+
if async_type == AsyncType.THREADING or async_type == AsyncType.THREADING.value:
|
|
44
|
+
thread = _Thread(target=job, *args, **kwarg)
|
|
45
|
+
thread.start()
|
|
46
|
+
return future
|
|
47
|
+
elif async_type == AsyncType.MULTIPROCESSING or async_type == AsyncType.MULTIPROCESSING.value:
|
|
48
|
+
process = _Process(target=job, *args, **kwarg)
|
|
49
|
+
process.start()
|
|
50
|
+
return future
|
|
51
|
+
|
|
52
|
+
raise ValueError("async_type")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import typing as _t
|
|
2
|
+
|
|
3
|
+
_T = _t.TypeVar('_T')
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LazySequence(_t.Sequence[_T]):
|
|
7
|
+
def __init__(self, iterable: _t.Iterable[_T]):
|
|
8
|
+
self._iterator = iter(iterable)
|
|
9
|
+
self._cache = []
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def cache(self):
|
|
13
|
+
return self._cache
|
|
14
|
+
|
|
15
|
+
def __iter__(self) -> _t.Iterator[_T]:
|
|
16
|
+
for item in self._cache:
|
|
17
|
+
yield item
|
|
18
|
+
|
|
19
|
+
for item in self._iterator:
|
|
20
|
+
self._cache.append(item)
|
|
21
|
+
yield item
|
|
22
|
+
|
|
23
|
+
def __len__(self) -> int:
|
|
24
|
+
return len(self)
|
|
25
|
+
|
|
26
|
+
def __getitem__(self, index: int) -> _T:
|
|
27
|
+
i = 0
|
|
28
|
+
for item in self:
|
|
29
|
+
if i == index:
|
|
30
|
+
return item
|
|
31
|
+
i += 1
|
|
32
|
+
raise IndexError()
|
|
33
|
+
|
|
34
|
+
def __contains__(self, item: object) -> bool:
|
|
35
|
+
for x in self:
|
|
36
|
+
if x == item:
|
|
37
|
+
return True
|
|
38
|
+
return False
|