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.
@@ -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,7 @@
1
+ # from ._multipledispatch import dispatch
2
+ from ._lang import *
3
+ from ._functions import *
4
+ from ._lazy import Lazy
5
+ from ._segment import Segment
6
+ from ._range import Range
7
+ from ._result import Result
@@ -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,2 @@
1
+ from ._lazy_sequence import LazySequence
2
+ from ._stream import Stream
@@ -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