pyrubix 1.0.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.
- pyrubix-1.0.0/PKG-INFO +47 -0
- pyrubix-1.0.0/README.md +39 -0
- pyrubix-1.0.0/pyproject.toml +13 -0
- pyrubix-1.0.0/pyrubix.egg-info/PKG-INFO +47 -0
- pyrubix-1.0.0/pyrubix.egg-info/SOURCES.txt +22 -0
- pyrubix-1.0.0/pyrubix.egg-info/dependency_links.txt +1 -0
- pyrubix-1.0.0/pyrubix.egg-info/top_level.txt +1 -0
- pyrubix-1.0.0/setup.cfg +4 -0
- pyrubix-1.0.0/ulib/__init__.py +221 -0
- pyrubix-1.0.0/ulib/cache.py +73 -0
- pyrubix-1.0.0/ulib/collections.py +41 -0
- pyrubix-1.0.0/ulib/crypto.py +47 -0
- pyrubix-1.0.0/ulib/decorators.py +50 -0
- pyrubix-1.0.0/ulib/fileutil.py +61 -0
- pyrubix-1.0.0/ulib/identity.py +9 -0
- pyrubix-1.0.0/ulib/jsonx.py +28 -0
- pyrubix-1.0.0/ulib/loggingx.py +9 -0
- pyrubix-1.0.0/ulib/math.py +83 -0
- pyrubix-1.0.0/ulib/network.py +41 -0
- pyrubix-1.0.0/ulib/process.py +22 -0
- pyrubix-1.0.0/ulib/randomutil.py +31 -0
- pyrubix-1.0.0/ulib/system.py +47 -0
- pyrubix-1.0.0/ulib/text.py +87 -0
- pyrubix-1.0.0/ulib/timeutil.py +18 -0
pyrubix-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyrubix
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Powerful lightweight Python utility library
|
|
5
|
+
Project-URL: Homepage, https://xnovear.dpdns.org
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# ULIB
|
|
10
|
+
|
|
11
|
+
Lightweight Python utility library.
|
|
12
|
+
|
|
13
|
+
Version: 3.0.0
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- Math
|
|
18
|
+
- Text
|
|
19
|
+
- JSON
|
|
20
|
+
- System
|
|
21
|
+
- Time
|
|
22
|
+
- Cache
|
|
23
|
+
- Retry
|
|
24
|
+
- Timer
|
|
25
|
+
- File utilities
|
|
26
|
+
- Subprocess
|
|
27
|
+
- Hash
|
|
28
|
+
- UUID
|
|
29
|
+
- Random
|
|
30
|
+
- Collections
|
|
31
|
+
- Network
|
|
32
|
+
- Logging
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
python -m pip install -e .
|
|
37
|
+
|
|
38
|
+
## Example
|
|
39
|
+
|
|
40
|
+
import ulib
|
|
41
|
+
|
|
42
|
+
print(ulib.add(10, 20))
|
|
43
|
+
print(ulib.sha256("hello"))
|
|
44
|
+
print(ulib.uuid())
|
|
45
|
+
|
|
46
|
+
result = ulib.run("ls -la")
|
|
47
|
+
print(result.stdout)
|
pyrubix-1.0.0/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# ULIB
|
|
2
|
+
|
|
3
|
+
Lightweight Python utility library.
|
|
4
|
+
|
|
5
|
+
Version: 3.0.0
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Math
|
|
10
|
+
- Text
|
|
11
|
+
- JSON
|
|
12
|
+
- System
|
|
13
|
+
- Time
|
|
14
|
+
- Cache
|
|
15
|
+
- Retry
|
|
16
|
+
- Timer
|
|
17
|
+
- File utilities
|
|
18
|
+
- Subprocess
|
|
19
|
+
- Hash
|
|
20
|
+
- UUID
|
|
21
|
+
- Random
|
|
22
|
+
- Collections
|
|
23
|
+
- Network
|
|
24
|
+
- Logging
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
python -m pip install -e .
|
|
29
|
+
|
|
30
|
+
## Example
|
|
31
|
+
|
|
32
|
+
import ulib
|
|
33
|
+
|
|
34
|
+
print(ulib.add(10, 20))
|
|
35
|
+
print(ulib.sha256("hello"))
|
|
36
|
+
print(ulib.uuid())
|
|
37
|
+
|
|
38
|
+
result = ulib.run("ls -la")
|
|
39
|
+
print(result.stdout)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyrubix"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Powerful lightweight Python utility library"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
|
|
12
|
+
[project.urls]
|
|
13
|
+
Homepage = "https://xnovear.dpdns.org"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyrubix
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Powerful lightweight Python utility library
|
|
5
|
+
Project-URL: Homepage, https://xnovear.dpdns.org
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# ULIB
|
|
10
|
+
|
|
11
|
+
Lightweight Python utility library.
|
|
12
|
+
|
|
13
|
+
Version: 3.0.0
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- Math
|
|
18
|
+
- Text
|
|
19
|
+
- JSON
|
|
20
|
+
- System
|
|
21
|
+
- Time
|
|
22
|
+
- Cache
|
|
23
|
+
- Retry
|
|
24
|
+
- Timer
|
|
25
|
+
- File utilities
|
|
26
|
+
- Subprocess
|
|
27
|
+
- Hash
|
|
28
|
+
- UUID
|
|
29
|
+
- Random
|
|
30
|
+
- Collections
|
|
31
|
+
- Network
|
|
32
|
+
- Logging
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
python -m pip install -e .
|
|
37
|
+
|
|
38
|
+
## Example
|
|
39
|
+
|
|
40
|
+
import ulib
|
|
41
|
+
|
|
42
|
+
print(ulib.add(10, 20))
|
|
43
|
+
print(ulib.sha256("hello"))
|
|
44
|
+
print(ulib.uuid())
|
|
45
|
+
|
|
46
|
+
result = ulib.run("ls -la")
|
|
47
|
+
print(result.stdout)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
pyrubix.egg-info/PKG-INFO
|
|
4
|
+
pyrubix.egg-info/SOURCES.txt
|
|
5
|
+
pyrubix.egg-info/dependency_links.txt
|
|
6
|
+
pyrubix.egg-info/top_level.txt
|
|
7
|
+
ulib/__init__.py
|
|
8
|
+
ulib/cache.py
|
|
9
|
+
ulib/collections.py
|
|
10
|
+
ulib/crypto.py
|
|
11
|
+
ulib/decorators.py
|
|
12
|
+
ulib/fileutil.py
|
|
13
|
+
ulib/identity.py
|
|
14
|
+
ulib/jsonx.py
|
|
15
|
+
ulib/loggingx.py
|
|
16
|
+
ulib/math.py
|
|
17
|
+
ulib/network.py
|
|
18
|
+
ulib/process.py
|
|
19
|
+
ulib/randomutil.py
|
|
20
|
+
ulib/system.py
|
|
21
|
+
ulib/text.py
|
|
22
|
+
ulib/timeutil.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ulib
|
pyrubix-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
from .math import (
|
|
2
|
+
add,
|
|
3
|
+
subtract,
|
|
4
|
+
multiply,
|
|
5
|
+
divide,
|
|
6
|
+
power,
|
|
7
|
+
average,
|
|
8
|
+
median,
|
|
9
|
+
clamp,
|
|
10
|
+
percentage,
|
|
11
|
+
lerp,
|
|
12
|
+
map_range,
|
|
13
|
+
factorial,
|
|
14
|
+
gcd,
|
|
15
|
+
lcm,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .text import (
|
|
19
|
+
reverse,
|
|
20
|
+
is_palindrome,
|
|
21
|
+
word_count,
|
|
22
|
+
char_count,
|
|
23
|
+
slugify,
|
|
24
|
+
truncate,
|
|
25
|
+
camel_case,
|
|
26
|
+
snake_case,
|
|
27
|
+
clean,
|
|
28
|
+
remove_spaces,
|
|
29
|
+
is_email,
|
|
30
|
+
is_url,
|
|
31
|
+
is_number,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from .jsonx import (
|
|
35
|
+
dumps,
|
|
36
|
+
loads,
|
|
37
|
+
save,
|
|
38
|
+
load,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
from .system import (
|
|
42
|
+
platform,
|
|
43
|
+
python_version,
|
|
44
|
+
cpu_count,
|
|
45
|
+
memory_usage,
|
|
46
|
+
env,
|
|
47
|
+
set_env,
|
|
48
|
+
which,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
from .timeutil import (
|
|
52
|
+
now,
|
|
53
|
+
timestamp,
|
|
54
|
+
elapsed,
|
|
55
|
+
sleep,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
from .cache import (
|
|
59
|
+
memoize,
|
|
60
|
+
TTLCache,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
from .decorators import (
|
|
64
|
+
timer,
|
|
65
|
+
retry,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
from .fileutil import (
|
|
69
|
+
read_text,
|
|
70
|
+
write_text,
|
|
71
|
+
read_bytes,
|
|
72
|
+
write_bytes,
|
|
73
|
+
exists,
|
|
74
|
+
is_file,
|
|
75
|
+
is_dir,
|
|
76
|
+
file_size,
|
|
77
|
+
copy,
|
|
78
|
+
move,
|
|
79
|
+
remove,
|
|
80
|
+
mkdir,
|
|
81
|
+
listdir,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
from .process import (
|
|
85
|
+
run,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
from .crypto import (
|
|
89
|
+
md5,
|
|
90
|
+
sha256,
|
|
91
|
+
sha512,
|
|
92
|
+
file_md5,
|
|
93
|
+
file_sha256,
|
|
94
|
+
file_sha512,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
from .identity import (
|
|
98
|
+
uuid,
|
|
99
|
+
short_uuid,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
from .randomutil import (
|
|
103
|
+
random_int,
|
|
104
|
+
random_float,
|
|
105
|
+
random_choice,
|
|
106
|
+
random_string,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
from .collections import (
|
|
110
|
+
chunk,
|
|
111
|
+
flatten,
|
|
112
|
+
unique,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
from .network import (
|
|
116
|
+
is_online,
|
|
117
|
+
ping,
|
|
118
|
+
get_public_ip,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
from .loggingx import (
|
|
122
|
+
log,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
__version__ = "3.0.0"
|
|
126
|
+
|
|
127
|
+
__all__ = [
|
|
128
|
+
"add",
|
|
129
|
+
"subtract",
|
|
130
|
+
"multiply",
|
|
131
|
+
"divide",
|
|
132
|
+
"power",
|
|
133
|
+
"average",
|
|
134
|
+
"median",
|
|
135
|
+
"clamp",
|
|
136
|
+
"percentage",
|
|
137
|
+
"lerp",
|
|
138
|
+
"map_range",
|
|
139
|
+
"factorial",
|
|
140
|
+
"gcd",
|
|
141
|
+
"lcm",
|
|
142
|
+
|
|
143
|
+
"reverse",
|
|
144
|
+
"is_palindrome",
|
|
145
|
+
"word_count",
|
|
146
|
+
"char_count",
|
|
147
|
+
"slugify",
|
|
148
|
+
"truncate",
|
|
149
|
+
"camel_case",
|
|
150
|
+
"snake_case",
|
|
151
|
+
"clean",
|
|
152
|
+
"remove_spaces",
|
|
153
|
+
"is_email",
|
|
154
|
+
"is_url",
|
|
155
|
+
"is_number",
|
|
156
|
+
|
|
157
|
+
"dumps",
|
|
158
|
+
"loads",
|
|
159
|
+
"save",
|
|
160
|
+
"load",
|
|
161
|
+
|
|
162
|
+
"platform",
|
|
163
|
+
"python_version",
|
|
164
|
+
"cpu_count",
|
|
165
|
+
"memory_usage",
|
|
166
|
+
"env",
|
|
167
|
+
"set_env",
|
|
168
|
+
"which",
|
|
169
|
+
|
|
170
|
+
"now",
|
|
171
|
+
"timestamp",
|
|
172
|
+
"elapsed",
|
|
173
|
+
"sleep",
|
|
174
|
+
|
|
175
|
+
"memoize",
|
|
176
|
+
"TTLCache",
|
|
177
|
+
|
|
178
|
+
"timer",
|
|
179
|
+
"retry",
|
|
180
|
+
|
|
181
|
+
"read_text",
|
|
182
|
+
"write_text",
|
|
183
|
+
"read_bytes",
|
|
184
|
+
"write_bytes",
|
|
185
|
+
"exists",
|
|
186
|
+
"is_file",
|
|
187
|
+
"is_dir",
|
|
188
|
+
"file_size",
|
|
189
|
+
"copy",
|
|
190
|
+
"move",
|
|
191
|
+
"remove",
|
|
192
|
+
"mkdir",
|
|
193
|
+
"listdir",
|
|
194
|
+
|
|
195
|
+
"run",
|
|
196
|
+
|
|
197
|
+
"md5",
|
|
198
|
+
"sha256",
|
|
199
|
+
"sha512",
|
|
200
|
+
"file_md5",
|
|
201
|
+
"file_sha256",
|
|
202
|
+
"file_sha512",
|
|
203
|
+
|
|
204
|
+
"uuid",
|
|
205
|
+
"short_uuid",
|
|
206
|
+
|
|
207
|
+
"random_int",
|
|
208
|
+
"random_float",
|
|
209
|
+
"random_choice",
|
|
210
|
+
"random_string",
|
|
211
|
+
|
|
212
|
+
"chunk",
|
|
213
|
+
"flatten",
|
|
214
|
+
"unique",
|
|
215
|
+
|
|
216
|
+
"is_online",
|
|
217
|
+
"ping",
|
|
218
|
+
"get_public_ip",
|
|
219
|
+
|
|
220
|
+
"log",
|
|
221
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def memoize(func):
|
|
6
|
+
cache = {}
|
|
7
|
+
|
|
8
|
+
@wraps(func)
|
|
9
|
+
def wrapper(*args, **kwargs):
|
|
10
|
+
key = (
|
|
11
|
+
args,
|
|
12
|
+
tuple(sorted(kwargs.items())),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if key not in cache:
|
|
16
|
+
cache[key] = func(*args, **kwargs)
|
|
17
|
+
|
|
18
|
+
return cache[key]
|
|
19
|
+
|
|
20
|
+
wrapper.cache = cache
|
|
21
|
+
|
|
22
|
+
return wrapper
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TTLCache:
|
|
26
|
+
def __init__(self, ttl=60):
|
|
27
|
+
if ttl < 0:
|
|
28
|
+
raise ValueError("ttl cannot be negative")
|
|
29
|
+
|
|
30
|
+
self.ttl = ttl
|
|
31
|
+
self._data = {}
|
|
32
|
+
|
|
33
|
+
def set(self, key, value):
|
|
34
|
+
self._data[key] = (
|
|
35
|
+
value,
|
|
36
|
+
time.monotonic() + self.ttl,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def get(self, key, default=None):
|
|
40
|
+
item = self._data.get(key)
|
|
41
|
+
|
|
42
|
+
if item is None:
|
|
43
|
+
return default
|
|
44
|
+
|
|
45
|
+
value, expires = item
|
|
46
|
+
|
|
47
|
+
if time.monotonic() >= expires:
|
|
48
|
+
self._data.pop(key, None)
|
|
49
|
+
return default
|
|
50
|
+
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
def delete(self, key):
|
|
54
|
+
self._data.pop(key, None)
|
|
55
|
+
|
|
56
|
+
def clear(self):
|
|
57
|
+
self._data.clear()
|
|
58
|
+
|
|
59
|
+
def __len__(self):
|
|
60
|
+
self._cleanup()
|
|
61
|
+
return len(self._data)
|
|
62
|
+
|
|
63
|
+
def _cleanup(self):
|
|
64
|
+
current = time.monotonic()
|
|
65
|
+
|
|
66
|
+
expired = [
|
|
67
|
+
key
|
|
68
|
+
for key, (_, expires) in self._data.items()
|
|
69
|
+
if current >= expires
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
for key in expired:
|
|
73
|
+
self._data.pop(key, None)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
def chunk(values, size):
|
|
2
|
+
if size <= 0:
|
|
3
|
+
raise ValueError("size must be greater than zero")
|
|
4
|
+
|
|
5
|
+
values = list(values)
|
|
6
|
+
|
|
7
|
+
return [
|
|
8
|
+
values[i:i + size]
|
|
9
|
+
for i in range(0, len(values), size)
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def flatten(values):
|
|
14
|
+
result = []
|
|
15
|
+
|
|
16
|
+
for item in values:
|
|
17
|
+
if isinstance(item, (list, tuple)):
|
|
18
|
+
result.extend(flatten(item))
|
|
19
|
+
else:
|
|
20
|
+
result.append(item)
|
|
21
|
+
|
|
22
|
+
return result
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def unique(values):
|
|
26
|
+
result = []
|
|
27
|
+
seen = set()
|
|
28
|
+
|
|
29
|
+
for item in values:
|
|
30
|
+
try:
|
|
31
|
+
key = item
|
|
32
|
+
|
|
33
|
+
if key not in seen:
|
|
34
|
+
seen.add(key)
|
|
35
|
+
result.append(item)
|
|
36
|
+
|
|
37
|
+
except TypeError:
|
|
38
|
+
if item not in result:
|
|
39
|
+
result.append(item)
|
|
40
|
+
|
|
41
|
+
return result
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _hash_text(text, algorithm):
|
|
5
|
+
return hashlib.new(
|
|
6
|
+
algorithm,
|
|
7
|
+
str(text).encode("utf-8"),
|
|
8
|
+
).hexdigest()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def md5(text):
|
|
12
|
+
return _hash_text(text, "md5")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def sha256(text):
|
|
16
|
+
return _hash_text(text, "sha256")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def sha512(text):
|
|
20
|
+
return _hash_text(text, "sha512")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _hash_file(path, algorithm, chunk_size=1024 * 1024):
|
|
24
|
+
h = hashlib.new(algorithm)
|
|
25
|
+
|
|
26
|
+
with open(path, "rb") as f:
|
|
27
|
+
while True:
|
|
28
|
+
chunk = f.read(chunk_size)
|
|
29
|
+
|
|
30
|
+
if not chunk:
|
|
31
|
+
break
|
|
32
|
+
|
|
33
|
+
h.update(chunk)
|
|
34
|
+
|
|
35
|
+
return h.hexdigest()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def file_md5(path):
|
|
39
|
+
return _hash_file(path, "md5")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def file_sha256(path):
|
|
43
|
+
return _hash_file(path, "sha256")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def file_sha512(path):
|
|
47
|
+
return _hash_file(path, "sha512")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def timer(func):
|
|
6
|
+
@wraps(func)
|
|
7
|
+
def wrapper(*args, **kwargs):
|
|
8
|
+
start = time.perf_counter()
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
return func(*args, **kwargs)
|
|
12
|
+
finally:
|
|
13
|
+
elapsed = time.perf_counter() - start
|
|
14
|
+
print(f"[ULIB] {func.__name__}: {elapsed:.6f}s")
|
|
15
|
+
|
|
16
|
+
return wrapper
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def retry(
|
|
20
|
+
attempts=3,
|
|
21
|
+
delay=0,
|
|
22
|
+
exceptions=(Exception,),
|
|
23
|
+
):
|
|
24
|
+
if attempts < 1:
|
|
25
|
+
raise ValueError("attempts must be >= 1")
|
|
26
|
+
|
|
27
|
+
if delay < 0:
|
|
28
|
+
raise ValueError("delay cannot be negative")
|
|
29
|
+
|
|
30
|
+
def decorator(func):
|
|
31
|
+
@wraps(func)
|
|
32
|
+
def wrapper(*args, **kwargs):
|
|
33
|
+
last_error = None
|
|
34
|
+
|
|
35
|
+
for attempt in range(attempts):
|
|
36
|
+
try:
|
|
37
|
+
return func(*args, **kwargs)
|
|
38
|
+
|
|
39
|
+
except exceptions as error:
|
|
40
|
+
last_error = error
|
|
41
|
+
|
|
42
|
+
if attempt < attempts - 1:
|
|
43
|
+
if delay:
|
|
44
|
+
time.sleep(delay)
|
|
45
|
+
|
|
46
|
+
raise last_error
|
|
47
|
+
|
|
48
|
+
return wrapper
|
|
49
|
+
|
|
50
|
+
return decorator
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def read_text(path, encoding="utf-8"):
|
|
6
|
+
with open(path, "r", encoding=encoding) as f:
|
|
7
|
+
return f.read()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def write_text(path, text, encoding="utf-8"):
|
|
11
|
+
with open(path, "w", encoding=encoding) as f:
|
|
12
|
+
f.write(str(text))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def read_bytes(path):
|
|
16
|
+
with open(path, "rb") as f:
|
|
17
|
+
return f.read()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def write_bytes(path, data):
|
|
21
|
+
with open(path, "wb") as f:
|
|
22
|
+
f.write(data)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def exists(path):
|
|
26
|
+
return os.path.exists(path)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_file(path):
|
|
30
|
+
return os.path.isfile(path)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_dir(path):
|
|
34
|
+
return os.path.isdir(path)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def file_size(path):
|
|
38
|
+
return os.path.getsize(path)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def copy(src, dst):
|
|
42
|
+
return shutil.copy2(src, dst)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def move(src, dst):
|
|
46
|
+
return shutil.move(src, dst)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def remove(path):
|
|
50
|
+
if os.path.isdir(path) and not os.path.islink(path):
|
|
51
|
+
shutil.rmtree(path)
|
|
52
|
+
else:
|
|
53
|
+
os.remove(path)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def mkdir(path, parents=True, exist_ok=True):
|
|
57
|
+
os.makedirs(path, exist_ok=exist_ok) if parents else os.mkdir(path)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def listdir(path="."):
|
|
61
|
+
return os.listdir(path)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def dumps(obj, indent=2, ensure_ascii=False):
|
|
5
|
+
return json.dumps(
|
|
6
|
+
obj,
|
|
7
|
+
indent=indent,
|
|
8
|
+
ensure_ascii=ensure_ascii,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def loads(text):
|
|
13
|
+
return json.loads(text)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def save(obj, path, indent=2):
|
|
17
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
18
|
+
json.dump(
|
|
19
|
+
obj,
|
|
20
|
+
f,
|
|
21
|
+
indent=indent,
|
|
22
|
+
ensure_ascii=False,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load(path):
|
|
27
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
28
|
+
return json.load(f)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def add(a, b):
|
|
5
|
+
return a + b
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def subtract(a, b):
|
|
9
|
+
return a - b
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def multiply(a, b):
|
|
13
|
+
return a * b
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def divide(a, b):
|
|
17
|
+
if b == 0:
|
|
18
|
+
raise ZeroDivisionError("cannot divide by zero")
|
|
19
|
+
return a / b
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def power(a, b):
|
|
23
|
+
return a ** b
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def average(values):
|
|
27
|
+
values = list(values)
|
|
28
|
+
|
|
29
|
+
if not values:
|
|
30
|
+
raise ValueError("values cannot be empty")
|
|
31
|
+
|
|
32
|
+
return sum(values) / len(values)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def median(values):
|
|
36
|
+
values = sorted(values)
|
|
37
|
+
|
|
38
|
+
if not values:
|
|
39
|
+
raise ValueError("values cannot be empty")
|
|
40
|
+
|
|
41
|
+
return math.median(values)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def clamp(value, minimum, maximum):
|
|
45
|
+
if minimum > maximum:
|
|
46
|
+
raise ValueError("minimum cannot be greater than maximum")
|
|
47
|
+
|
|
48
|
+
return max(minimum, min(value, maximum))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def percentage(value, total):
|
|
52
|
+
if total == 0:
|
|
53
|
+
raise ZeroDivisionError("total cannot be zero")
|
|
54
|
+
|
|
55
|
+
return value / total * 100
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def lerp(a, b, t):
|
|
59
|
+
return a + (b - a) * t
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def map_range(value, in_min, in_max, out_min, out_max):
|
|
63
|
+
if in_min == in_max:
|
|
64
|
+
raise ValueError("input range cannot be zero")
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
(value - in_min)
|
|
68
|
+
* (out_max - out_min)
|
|
69
|
+
/ (in_max - in_min)
|
|
70
|
+
+ out_min
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def factorial(n):
|
|
75
|
+
return math.factorial(n)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def gcd(a, b):
|
|
79
|
+
return math.gcd(a, b)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def lcm(a, b):
|
|
83
|
+
return math.lcm(a, b)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import urllib.request
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def is_online(timeout=3):
|
|
6
|
+
try:
|
|
7
|
+
with socket.create_connection(
|
|
8
|
+
("1.1.1.1", 53),
|
|
9
|
+
timeout=timeout,
|
|
10
|
+
):
|
|
11
|
+
return True
|
|
12
|
+
|
|
13
|
+
except OSError:
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def ping(host, port=80, timeout=3):
|
|
18
|
+
try:
|
|
19
|
+
with socket.create_connection(
|
|
20
|
+
(host, port),
|
|
21
|
+
timeout=timeout,
|
|
22
|
+
):
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
except OSError:
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_public_ip(timeout=5):
|
|
30
|
+
request = urllib.request.Request(
|
|
31
|
+
"https://api.ipify.org",
|
|
32
|
+
headers={
|
|
33
|
+
"User-Agent": "ulib/3.0",
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
with urllib.request.urlopen(
|
|
38
|
+
request,
|
|
39
|
+
timeout=timeout,
|
|
40
|
+
) as response:
|
|
41
|
+
return response.read().decode("utf-8").strip()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def run(
|
|
5
|
+
command,
|
|
6
|
+
*,
|
|
7
|
+
shell=True,
|
|
8
|
+
check=False,
|
|
9
|
+
capture=True,
|
|
10
|
+
text=True,
|
|
11
|
+
timeout=None,
|
|
12
|
+
):
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
command,
|
|
15
|
+
shell=shell,
|
|
16
|
+
check=check,
|
|
17
|
+
capture_output=capture,
|
|
18
|
+
text=text,
|
|
19
|
+
timeout=timeout,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
return result
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import string
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def random_int(minimum, maximum):
|
|
6
|
+
return random.randint(minimum, maximum)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def random_float(minimum=0.0, maximum=1.0):
|
|
10
|
+
return random.uniform(minimum, maximum)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def random_choice(values):
|
|
14
|
+
values = list(values)
|
|
15
|
+
|
|
16
|
+
if not values:
|
|
17
|
+
raise ValueError("values cannot be empty")
|
|
18
|
+
|
|
19
|
+
return random.choice(values)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def random_string(length):
|
|
23
|
+
if length < 0:
|
|
24
|
+
raise ValueError("length cannot be negative")
|
|
25
|
+
|
|
26
|
+
alphabet = string.ascii_letters + string.digits
|
|
27
|
+
|
|
28
|
+
return "".join(
|
|
29
|
+
random.choice(alphabet)
|
|
30
|
+
for _ in range(length)
|
|
31
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform as _platform
|
|
3
|
+
import shutil
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import resource
|
|
8
|
+
except ImportError:
|
|
9
|
+
resource = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def platform():
|
|
13
|
+
return _platform.platform()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def python_version():
|
|
17
|
+
return _platform.python_version()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def cpu_count():
|
|
21
|
+
return os.cpu_count() or 1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def memory_usage():
|
|
25
|
+
if resource is None:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
usage = resource.getrusage(resource.RUSAGE_SELF)
|
|
29
|
+
|
|
30
|
+
value = usage.ru_maxrss
|
|
31
|
+
|
|
32
|
+
if sys.platform == "darwin":
|
|
33
|
+
return value
|
|
34
|
+
|
|
35
|
+
return value * 1024
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def env(name, default=None):
|
|
39
|
+
return os.environ.get(name, default)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def set_env(name, value):
|
|
43
|
+
os.environ[str(name)] = str(value)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def which(command):
|
|
47
|
+
return shutil.which(command)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def reverse(text):
|
|
5
|
+
return text[::-1]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def is_palindrome(text):
|
|
9
|
+
cleaned = "".join(c.lower() for c in text if c.isalnum())
|
|
10
|
+
return cleaned == cleaned[::-1]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def word_count(text):
|
|
14
|
+
return len(text.split())
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def char_count(text, include_spaces=False):
|
|
18
|
+
if include_spaces:
|
|
19
|
+
return len(text)
|
|
20
|
+
|
|
21
|
+
return len("".join(text.split()))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def slugify(text):
|
|
25
|
+
text = text.strip().lower()
|
|
26
|
+
text = re.sub(r"[^0-9a-z가-힣]+", "-", text)
|
|
27
|
+
return text.strip("-")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def truncate(text, length, suffix="..."):
|
|
31
|
+
if length < 0:
|
|
32
|
+
raise ValueError("length cannot be negative")
|
|
33
|
+
|
|
34
|
+
if len(text) <= length:
|
|
35
|
+
return text
|
|
36
|
+
|
|
37
|
+
if length <= len(suffix):
|
|
38
|
+
return suffix[:length]
|
|
39
|
+
|
|
40
|
+
return text[:length - len(suffix)] + suffix
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def camel_case(text):
|
|
44
|
+
parts = re.split(r"[\s_\-]+", text.strip())
|
|
45
|
+
|
|
46
|
+
if not parts:
|
|
47
|
+
return ""
|
|
48
|
+
|
|
49
|
+
return parts[0].lower() + "".join(
|
|
50
|
+
part[:1].upper() + part[1:].lower()
|
|
51
|
+
for part in parts[1:]
|
|
52
|
+
if part
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def snake_case(text):
|
|
57
|
+
text = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", text)
|
|
58
|
+
text = re.sub(r"[\s\-]+", "_", text)
|
|
59
|
+
text = re.sub(r"[^0-9a-zA-Z가-힣_]", "", text)
|
|
60
|
+
|
|
61
|
+
return text.lower().strip("_")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def clean(text):
|
|
65
|
+
return " ".join(text.split())
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def remove_spaces(text):
|
|
69
|
+
return "".join(text.split())
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def is_email(text):
|
|
73
|
+
pattern = r"^[^@\s]+@[^@\s]+\.[^@\s]+$"
|
|
74
|
+
return bool(re.match(pattern, text))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def is_url(text):
|
|
78
|
+
pattern = r"^https?://[^\s]+$"
|
|
79
|
+
return bool(re.match(pattern, text))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def is_number(text):
|
|
83
|
+
try:
|
|
84
|
+
float(text)
|
|
85
|
+
return True
|
|
86
|
+
except (TypeError, ValueError):
|
|
87
|
+
return False
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def now():
|
|
6
|
+
return datetime.now(timezone.utc)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def timestamp():
|
|
10
|
+
return time.time()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def elapsed(start):
|
|
14
|
+
return time.perf_counter() - start
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def sleep(seconds):
|
|
18
|
+
time.sleep(seconds)
|