py3-kit 0.0.8__py3-none-any.whl
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.
- py3_kit/__datetime.py +23 -0
- py3_kit/__init__.py +35 -0
- py3_kit/__py3_execute__/__init__.py +1 -0
- py3_kit/__py3_logger__/__init__.py +1 -0
- py3_kit/__py3_web__/__init__.py +1 -0
- py3_kit/__string.py +31 -0
- py3_kit/_math.py +62 -0
- py3_kit/_pandas.py +30 -0
- py3_kit/_types.py +50 -0
- py3_kit/assets/file/linux/7zz +0 -0
- py3_kit/assets/file/linux/7zzs +0 -0
- py3_kit/assets/file/linux/rar +0 -0
- py3_kit/assets/file/linux/unrar +0 -0
- py3_kit/assets/file/mac/7zz +0 -0
- py3_kit/assets/file/mac/rar +0 -0
- py3_kit/assets/file/mac/unrar +0 -0
- py3_kit/assets/file/win/7zr.exe +0 -0
- py3_kit/assets/file/win/Rar.exe +0 -0
- py3_kit/assets/file/win/UnRAR.exe +0 -0
- py3_kit/assets.py +10 -0
- py3_kit/file.py +209 -0
- py3_kit/image.py +139 -0
- py3_kit/list.py +32 -0
- py3_kit/pdf.py +20 -0
- py3_kit/project.py +58 -0
- py3_kit/sql.py +15 -0
- py3_kit/validators.py +21 -0
- py3_kit-0.0.8.dist-info/METADATA +31 -0
- py3_kit-0.0.8.dist-info/RECORD +31 -0
- py3_kit-0.0.8.dist-info/WHEEL +4 -0
- py3_kit-0.0.8.dist-info/licenses/LICENSE +21 -0
py3_kit/__datetime.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import arrow
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def now(fmt: str = "YYYY-MM-DD HH:mm:ss") -> str:
|
|
5
|
+
return arrow.now().format(fmt)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def delta(start_datetime_str: str, end_datetime_str: str) -> int:
|
|
9
|
+
"""
|
|
10
|
+
>>> delta("2026-01-07 08:56:48","2026-01-07 13:00:00")
|
|
11
|
+
14592
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
start_datetime_str:
|
|
15
|
+
end_datetime_str:
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
start_datetime = arrow.get(start_datetime_str).datetime
|
|
21
|
+
end_datetime = arrow.get(end_datetime_str).datetime
|
|
22
|
+
seconds = (end_datetime - start_datetime).total_seconds()
|
|
23
|
+
return int(seconds)
|
py3_kit/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from . import __datetime as datetime
|
|
2
|
+
from . import __py3_execute__ as py3_execute
|
|
3
|
+
from . import __py3_logger__ as py3_logger
|
|
4
|
+
from . import __py3_web__ as py3_web
|
|
5
|
+
from . import __string as string
|
|
6
|
+
from . import _math as math
|
|
7
|
+
from . import _pandas as pandas
|
|
8
|
+
from . import _types as types
|
|
9
|
+
from . import assets
|
|
10
|
+
from . import file
|
|
11
|
+
from . import image
|
|
12
|
+
from . import list
|
|
13
|
+
from . import pdf
|
|
14
|
+
from . import project
|
|
15
|
+
from . import sql
|
|
16
|
+
from . import validators
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"py3_execute",
|
|
20
|
+
"py3_logger",
|
|
21
|
+
"py3_web",
|
|
22
|
+
"datetime",
|
|
23
|
+
"string",
|
|
24
|
+
"math",
|
|
25
|
+
"pandas",
|
|
26
|
+
"types",
|
|
27
|
+
"assets",
|
|
28
|
+
"file",
|
|
29
|
+
"image",
|
|
30
|
+
"list",
|
|
31
|
+
"pdf",
|
|
32
|
+
"project",
|
|
33
|
+
"sql",
|
|
34
|
+
"validators",
|
|
35
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from py3_execute import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from py3_logger import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from py3_web import *
|
py3_kit/__string.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def camel_to_snake(string: str) -> str:
|
|
5
|
+
"""
|
|
6
|
+
>>> camel_to_snake("camelToSnake")
|
|
7
|
+
'camel_to_snake'
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
string:
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
string = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", string)
|
|
16
|
+
string = re.sub("([a-z0-9])([A-Z])", r"\1_\2", string).lower()
|
|
17
|
+
return string
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def snake_to_camel(string: str) -> str:
|
|
21
|
+
"""
|
|
22
|
+
>>> snake_to_camel("snake_to_camel")
|
|
23
|
+
'SnakeToCamel'
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
string:
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
return "".join(i.capitalize() for i in string.split("_"))
|
py3_kit/_math.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def int_ceil(num: float) -> int:
|
|
5
|
+
"""
|
|
6
|
+
>>> int_ceil(10 / 3)
|
|
7
|
+
4
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
num:
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
return math.ceil(num)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def decimal_ceil(
|
|
19
|
+
num: float,
|
|
20
|
+
places: int = 2
|
|
21
|
+
) -> float:
|
|
22
|
+
"""
|
|
23
|
+
>>> decimal_ceil(3.14159)
|
|
24
|
+
3.15
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
num:
|
|
28
|
+
places:
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
if places >= 0:
|
|
34
|
+
scale = 10.0 ** places
|
|
35
|
+
return math.ceil(num * scale) / scale
|
|
36
|
+
else:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
f"Invalid value for 'places': "
|
|
39
|
+
f"Expected `int` and >= 0, "
|
|
40
|
+
f"but got value: {places!r}"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def format_number(
|
|
45
|
+
number: int | float | str,
|
|
46
|
+
places: int = 2
|
|
47
|
+
) -> str:
|
|
48
|
+
"""
|
|
49
|
+
>>> format_number(3.1)
|
|
50
|
+
'3.10'
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
number:
|
|
54
|
+
places:
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
integer_str, decimal_str = str(float(number)).split(".")
|
|
60
|
+
decimal_str = (decimal_str + "0" * (places - len(decimal_str)))[:places]
|
|
61
|
+
number_str = ".".join([integer_str, decimal_str])
|
|
62
|
+
return number_str
|
py3_kit/_pandas.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def split(
|
|
5
|
+
df: pd.DataFrame,
|
|
6
|
+
num_parts: int | None = None,
|
|
7
|
+
part_size: int | None = None
|
|
8
|
+
) -> list[pd.DataFrame]:
|
|
9
|
+
if df.empty:
|
|
10
|
+
return []
|
|
11
|
+
|
|
12
|
+
if num_parts:
|
|
13
|
+
avg_size = len(df) // num_parts
|
|
14
|
+
remainder = len(df) % num_parts
|
|
15
|
+
|
|
16
|
+
result = []
|
|
17
|
+
start = 0
|
|
18
|
+
for i in range(num_parts):
|
|
19
|
+
end = start + avg_size + (1 if i < remainder else 0)
|
|
20
|
+
result.append(df.iloc[start:end])
|
|
21
|
+
start = end
|
|
22
|
+
return result
|
|
23
|
+
|
|
24
|
+
elif part_size:
|
|
25
|
+
return [df.iloc[i:i + part_size] for i in range(0, len(df), part_size)]
|
|
26
|
+
|
|
27
|
+
else:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
f"Either num_parts: {num_parts!r} or part_size: {part_size!r} must be provided"
|
|
30
|
+
)
|
py3_kit/_types.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from abc import ABCMeta
|
|
2
|
+
from collections.abc import ItemsView, KeysView, ValuesView
|
|
3
|
+
from types import new_class
|
|
4
|
+
from typing import NamedTuple, cast
|
|
5
|
+
|
|
6
|
+
import py3_kit
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ViewClasses(NamedTuple):
|
|
10
|
+
ItemsView: type
|
|
11
|
+
KeysView: type
|
|
12
|
+
ValuesView: type
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_view_classes(class_name: str) -> ViewClasses:
|
|
16
|
+
def _create_view_classes(base_class: type) -> type:
|
|
17
|
+
name = f"{class_name}{base_class.__name__}"
|
|
18
|
+
return new_class(
|
|
19
|
+
name,
|
|
20
|
+
(base_class,),
|
|
21
|
+
exec_body=lambda ns: ns.update({
|
|
22
|
+
"__repr__": lambda self: f"{py3_kit.string.camel_to_snake(name)}({list(self)})"
|
|
23
|
+
})
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
return ViewClasses(
|
|
27
|
+
ItemsView=_create_view_classes(ItemsView),
|
|
28
|
+
KeysView=_create_view_classes(KeysView),
|
|
29
|
+
ValuesView=_create_view_classes(ValuesView),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def create_subclasscheck_meta_class(
|
|
34
|
+
meta_class_name: str = "SubclasscheckMeta",
|
|
35
|
+
*,
|
|
36
|
+
required_all_methods: tuple[str, ...] = tuple(),
|
|
37
|
+
required_any_methods: tuple[str, ...] = tuple()
|
|
38
|
+
) -> type[ABCMeta]:
|
|
39
|
+
return cast(type[ABCMeta], new_class(
|
|
40
|
+
meta_class_name,
|
|
41
|
+
(ABCMeta,),
|
|
42
|
+
exec_body=lambda ns: ns.update({
|
|
43
|
+
"__subclasscheck__": classmethod(
|
|
44
|
+
lambda cls, subclass:
|
|
45
|
+
all([callable(getattr(subclass, i, None)) for i in required_all_methods]) and
|
|
46
|
+
(any([callable(getattr(subclass, i, None)) for i in required_any_methods]) if required_any_methods
|
|
47
|
+
else True)
|
|
48
|
+
)
|
|
49
|
+
})
|
|
50
|
+
))
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
py3_kit/assets.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_assets_file_path(path: str) -> str:
|
|
6
|
+
assets_dir_path = os.path.splitext(os.path.abspath(__file__))[0]
|
|
7
|
+
caller_dir_path = os.path.splitext([str(frame_info.filename) for frame_info in inspect.stack()][1])[0]
|
|
8
|
+
mid_dir_path = os.path.relpath(caller_dir_path, assets_dir_path).lstrip(os.path.pardir + os.path.sep)
|
|
9
|
+
assets_file_path = os.path.join(assets_dir_path, mid_dir_path, path)
|
|
10
|
+
return assets_file_path
|
py3_kit/file.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
rar
|
|
3
|
+
https://www.rarlab.com/download.htm
|
|
4
|
+
|
|
5
|
+
7z
|
|
6
|
+
Windows: https://www.7-zip.org/a/7zr.exe
|
|
7
|
+
Darwin: https://www.7-zip.org/a/7z2501-mac.tar.xz
|
|
8
|
+
Linux: https://www.7-zip.org/a/7z2501-linux-x64.tar.xz
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
import platform
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import zipfile
|
|
15
|
+
from typing import Literal, cast
|
|
16
|
+
|
|
17
|
+
import py3_kit
|
|
18
|
+
|
|
19
|
+
COMPRESS_TYPE = Literal["zip", "rar", "7z"]
|
|
20
|
+
DECOMPRESS_TYPE = Literal["zip", "rar", "7z"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def compress(
|
|
24
|
+
path: str,
|
|
25
|
+
compress_type: COMPRESS_TYPE | None = None,
|
|
26
|
+
compress_file_path: str | None = None
|
|
27
|
+
) -> str | None:
|
|
28
|
+
path = os.path.abspath(path)
|
|
29
|
+
|
|
30
|
+
if compress_file_path is not None:
|
|
31
|
+
compress_file_path = os.path.abspath(compress_file_path)
|
|
32
|
+
if compress_type is None:
|
|
33
|
+
compress_type = os.path.splitext(compress_file_path)[-1][1:]
|
|
34
|
+
else:
|
|
35
|
+
if compress_type is None:
|
|
36
|
+
compress_type = "zip"
|
|
37
|
+
compress_file_path = os.path.join(
|
|
38
|
+
os.path.dirname(path), os.path.basename(path) + os.extsep + compress_type
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if compress_type == "zip":
|
|
42
|
+
with zipfile.ZipFile(compress_file_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
43
|
+
if os.path.isdir(path):
|
|
44
|
+
for root, dirs, files in os.walk(path):
|
|
45
|
+
for file in files:
|
|
46
|
+
if (file_path := os.path.join(root, file)) == compress_file_path:
|
|
47
|
+
continue
|
|
48
|
+
rel_file_path = os.path.relpath(file_path, path)
|
|
49
|
+
zf.write(file_path, arcname=rel_file_path)
|
|
50
|
+
else:
|
|
51
|
+
rel_file_path = os.path.basename(path)
|
|
52
|
+
zf.write(path, arcname=rel_file_path)
|
|
53
|
+
return compress_file_path
|
|
54
|
+
|
|
55
|
+
if compress_type == "rar":
|
|
56
|
+
if sys.platform.startswith("win"):
|
|
57
|
+
x = py3_kit.assets.get_assets_file_path(f"win{os.sep}Rar.exe")
|
|
58
|
+
elif sys.platform == "darwin":
|
|
59
|
+
x = py3_kit.assets.get_assets_file_path(f"mac{os.sep}rar")
|
|
60
|
+
elif sys.platform.startswith("linux"):
|
|
61
|
+
x = py3_kit.assets.get_assets_file_path(f"linux{os.sep}rar")
|
|
62
|
+
else:
|
|
63
|
+
return None
|
|
64
|
+
args = [
|
|
65
|
+
x,
|
|
66
|
+
"a",
|
|
67
|
+
"-r",
|
|
68
|
+
"-inul",
|
|
69
|
+
"-ep1",
|
|
70
|
+
compress_file_path
|
|
71
|
+
]
|
|
72
|
+
if os.path.isdir(path):
|
|
73
|
+
args.append(f"{path}{os.sep}*")
|
|
74
|
+
else:
|
|
75
|
+
args.append(path)
|
|
76
|
+
if subprocess.run(args).returncode == 0:
|
|
77
|
+
return compress_file_path
|
|
78
|
+
|
|
79
|
+
if compress_type == "7z":
|
|
80
|
+
if sys.platform.startswith("win"):
|
|
81
|
+
x = py3_kit.assets.get_assets_file_path(f"win{os.sep}7zr.exe")
|
|
82
|
+
elif sys.platform == "darwin":
|
|
83
|
+
x = py3_kit.assets.get_assets_file_path(f"mac{os.sep}7zz")
|
|
84
|
+
elif sys.platform.startswith("linux"):
|
|
85
|
+
x = py3_kit.assets.get_assets_file_path(f"linux{os.sep}7zz")
|
|
86
|
+
else:
|
|
87
|
+
return None
|
|
88
|
+
args = [
|
|
89
|
+
x,
|
|
90
|
+
"a",
|
|
91
|
+
compress_file_path
|
|
92
|
+
]
|
|
93
|
+
if os.path.isdir(path):
|
|
94
|
+
args.append(f"{path}{os.sep}*")
|
|
95
|
+
else:
|
|
96
|
+
args.append(path)
|
|
97
|
+
args.extend("-bb0 -bso0 -bsp0 -bse0".split())
|
|
98
|
+
if subprocess.run(args).returncode == 0:
|
|
99
|
+
return compress_file_path
|
|
100
|
+
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def decompress(
|
|
105
|
+
compress_file_path: str,
|
|
106
|
+
path: str | None = None
|
|
107
|
+
) -> str | None:
|
|
108
|
+
compress_file_path = os.path.abspath(compress_file_path)
|
|
109
|
+
if not os.path.isfile(compress_file_path):
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
if path is not None:
|
|
113
|
+
path = os.path.abspath(path)
|
|
114
|
+
else:
|
|
115
|
+
path = os.path.dirname(compress_file_path)
|
|
116
|
+
os.makedirs(path, exist_ok=True)
|
|
117
|
+
|
|
118
|
+
with open(compress_file_path, "rb") as file:
|
|
119
|
+
data = file.read(8)
|
|
120
|
+
text = data.hex()
|
|
121
|
+
if text in ("504b030414000000",):
|
|
122
|
+
decompress_type = "zip"
|
|
123
|
+
elif text in ("526172211a070100", "526172211a0700cf"):
|
|
124
|
+
decompress_type = "rar"
|
|
125
|
+
elif text in ('377abcaf271c0004',):
|
|
126
|
+
decompress_type = "7z"
|
|
127
|
+
else:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
if decompress_type == "zip":
|
|
131
|
+
with zipfile.ZipFile(compress_file_path, "r") as zf:
|
|
132
|
+
zf.extractall(path)
|
|
133
|
+
return path
|
|
134
|
+
|
|
135
|
+
if decompress_type == "rar":
|
|
136
|
+
if sys.platform.startswith("win"):
|
|
137
|
+
x = py3_kit.assets.get_assets_file_path(f"win{os.sep}UnRAR.exe")
|
|
138
|
+
elif sys.platform == "darwin":
|
|
139
|
+
x = py3_kit.assets.get_assets_file_path(f"mac{os.sep}unrar")
|
|
140
|
+
elif sys.platform.startswith("linux"):
|
|
141
|
+
x = py3_kit.assets.get_assets_file_path(f"linux{os.sep}unrar")
|
|
142
|
+
else:
|
|
143
|
+
return None
|
|
144
|
+
args = [
|
|
145
|
+
x,
|
|
146
|
+
"x",
|
|
147
|
+
"-o+",
|
|
148
|
+
"-inul",
|
|
149
|
+
compress_file_path,
|
|
150
|
+
path
|
|
151
|
+
]
|
|
152
|
+
if subprocess.run(args).returncode == 0:
|
|
153
|
+
return path
|
|
154
|
+
|
|
155
|
+
if decompress_type == "7z":
|
|
156
|
+
if sys.platform.startswith("win"):
|
|
157
|
+
x = py3_kit.assets.get_assets_file_path(f"win{os.sep}7zr.exe")
|
|
158
|
+
elif sys.platform == "darwin":
|
|
159
|
+
x = py3_kit.assets.get_assets_file_path(f"mac{os.sep}7zz")
|
|
160
|
+
elif sys.platform.startswith("linux"):
|
|
161
|
+
x = py3_kit.assets.get_assets_file_path(f"linux{os.sep}7zz")
|
|
162
|
+
else:
|
|
163
|
+
return None
|
|
164
|
+
args = [
|
|
165
|
+
x,
|
|
166
|
+
"x",
|
|
167
|
+
compress_file_path,
|
|
168
|
+
f"-o{path}",
|
|
169
|
+
"-y"
|
|
170
|
+
]
|
|
171
|
+
args.extend("-bb0 -bso0 -bsp0 -bse0".split())
|
|
172
|
+
if subprocess.run(args).returncode == 0:
|
|
173
|
+
return path
|
|
174
|
+
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_file_paths_and_dir_paths(path: str) -> tuple[list[str], list[str]]:
|
|
179
|
+
file_paths = []
|
|
180
|
+
dir_paths = []
|
|
181
|
+
|
|
182
|
+
path = os.path.abspath(path)
|
|
183
|
+
with os.scandir(path) as entries:
|
|
184
|
+
for entry in entries:
|
|
185
|
+
System = Literal["Windows", "Linux", "Darwin"]
|
|
186
|
+
system: System = cast(System, platform.system())
|
|
187
|
+
if system == "Windows":
|
|
188
|
+
from nt import DirEntry
|
|
189
|
+
elif system == "Linux":
|
|
190
|
+
from posix import DirEntry
|
|
191
|
+
elif system == "Darwin":
|
|
192
|
+
from posix import DirEntry
|
|
193
|
+
else:
|
|
194
|
+
raise TypeError(
|
|
195
|
+
f"Invalid type for 'system': "
|
|
196
|
+
f"Expected `Literal[\"Windows\",\"Linux\",\"Darwin\"]`, "
|
|
197
|
+
f"but got {type(system).__name__!r} (value: {system!r})"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
entry: DirEntry
|
|
201
|
+
if entry.is_file():
|
|
202
|
+
file_paths.append(entry.path)
|
|
203
|
+
elif entry.is_dir():
|
|
204
|
+
dir_paths.append(entry.path)
|
|
205
|
+
sub_file_paths, sub_dir_paths = get_file_paths_and_dir_paths(entry.path)
|
|
206
|
+
file_paths.extend(sub_file_paths)
|
|
207
|
+
dir_paths.extend(sub_dir_paths)
|
|
208
|
+
|
|
209
|
+
return file_paths, dir_paths
|
py3_kit/image.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from tempfile import NamedTemporaryFile
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import cv2
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pillow_avif # type: ignore
|
|
8
|
+
from PIL import Image
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def to_jpg(
|
|
12
|
+
image_file_path: str,
|
|
13
|
+
/,
|
|
14
|
+
jpg_file_path: str | None = None,
|
|
15
|
+
quality: int = 100,
|
|
16
|
+
keep_original: bool = False
|
|
17
|
+
) -> str | None:
|
|
18
|
+
try:
|
|
19
|
+
with open(image_file_path, "rb") as file:
|
|
20
|
+
prefix_text = file.read(3).hex()
|
|
21
|
+
n = 2
|
|
22
|
+
file.seek(-n, 2)
|
|
23
|
+
suffix_text = file.read(n).hex()
|
|
24
|
+
text = prefix_text + suffix_text
|
|
25
|
+
|
|
26
|
+
image_file_path = os.path.abspath(image_file_path)
|
|
27
|
+
if jpg_file_path is not None:
|
|
28
|
+
jpg_file_path = os.path.abspath(jpg_file_path)
|
|
29
|
+
else:
|
|
30
|
+
jpg_file_path = os.path.splitext(image_file_path)[0] + os.path.extsep + "jpg"
|
|
31
|
+
|
|
32
|
+
if image_file_path == jpg_file_path and text == "ffd8ffffd9":
|
|
33
|
+
return jpg_file_path
|
|
34
|
+
|
|
35
|
+
jpg_dir_path = os.path.dirname(jpg_file_path)
|
|
36
|
+
os.makedirs(jpg_dir_path, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
with Image.open(image_file_path) as image:
|
|
39
|
+
if image.mode in ("RGBA", "LA"):
|
|
40
|
+
background = Image.new("RGB", image.size, (255, 255, 255))
|
|
41
|
+
background.paste(image, mask=image.split()[-1])
|
|
42
|
+
image = background
|
|
43
|
+
elif image.mode == "P":
|
|
44
|
+
image.seek(0) # image.n_frames
|
|
45
|
+
image = image.convert("RGB")
|
|
46
|
+
elif image.mode == "RGB":
|
|
47
|
+
pass
|
|
48
|
+
else:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
with NamedTemporaryFile(suffix=os.path.extsep + "jpg", delete=False, dir=jpg_dir_path) as ntf:
|
|
52
|
+
temp_file_path = ntf.name
|
|
53
|
+
image.save(temp_file_path, "JPEG", quality=quality)
|
|
54
|
+
os.replace(temp_file_path, jpg_file_path)
|
|
55
|
+
|
|
56
|
+
if not keep_original:
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
if (
|
|
59
|
+
jpg_file_path != image_file_path and
|
|
60
|
+
(p := Path(image_file_path)).exists() and
|
|
61
|
+
str(p.resolve()) == str(p.absolute())
|
|
62
|
+
):
|
|
63
|
+
os.remove(image_file_path)
|
|
64
|
+
|
|
65
|
+
return jpg_file_path
|
|
66
|
+
except Exception as e: # noqa
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def resize(image: cv2.typing.MatLike, new_w: int, new_h: int) -> cv2.typing.MatLike:
|
|
71
|
+
return cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def concat(
|
|
75
|
+
input_image_file_paths: list[str],
|
|
76
|
+
output_image_file_path: str,
|
|
77
|
+
orientation: Literal["vertical", "horizontal"] = "vertical",
|
|
78
|
+
output_image_vertical_width: int | float | None = None,
|
|
79
|
+
output_image_horizontal_height: int | float | None = None
|
|
80
|
+
) -> bool:
|
|
81
|
+
try:
|
|
82
|
+
input_images = [cv2.imread(i, cv2.IMREAD_UNCHANGED) for i in input_image_file_paths]
|
|
83
|
+
|
|
84
|
+
if any(i is None for i in input_images):
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
if orientation == "vertical":
|
|
88
|
+
if output_image_vertical_width is None:
|
|
89
|
+
output_image_vertical_width = min(i.shape[1] for i in input_images)
|
|
90
|
+
|
|
91
|
+
resized_images = []
|
|
92
|
+
for i in input_images:
|
|
93
|
+
h, w = i.shape[:2]
|
|
94
|
+
new_w = int(output_image_vertical_width)
|
|
95
|
+
new_h = int(h * new_w / w)
|
|
96
|
+
resized_image = resize(i, new_w, new_h)
|
|
97
|
+
resized_images.append(resized_image)
|
|
98
|
+
|
|
99
|
+
output_image_height = sum(i.shape[0] for i in resized_images)
|
|
100
|
+
|
|
101
|
+
output_image = np.ones((output_image_height, output_image_vertical_width, 3), dtype=np.uint8) * 255
|
|
102
|
+
|
|
103
|
+
y = 0
|
|
104
|
+
for i in resized_images:
|
|
105
|
+
h = i.shape[0]
|
|
106
|
+
output_image[y:y + h, :i.shape[1]] = i
|
|
107
|
+
y += h
|
|
108
|
+
|
|
109
|
+
return cv2.imwrite(output_image_file_path, output_image)
|
|
110
|
+
|
|
111
|
+
elif orientation == "horizontal":
|
|
112
|
+
if output_image_horizontal_height is None:
|
|
113
|
+
output_image_horizontal_height = min(i.shape[0] for i in input_images)
|
|
114
|
+
|
|
115
|
+
resized_images = []
|
|
116
|
+
for i in input_images:
|
|
117
|
+
h, w = i.shape[:2]
|
|
118
|
+
new_h = int(output_image_horizontal_height)
|
|
119
|
+
new_w = int(w * new_h / h)
|
|
120
|
+
resized_image = resize(i, new_w, new_h)
|
|
121
|
+
resized_images.append(resized_image)
|
|
122
|
+
|
|
123
|
+
output_image_width = sum(i.shape[1] for i in resized_images)
|
|
124
|
+
|
|
125
|
+
output_image = np.ones((output_image_horizontal_height, output_image_width, 3), dtype=np.uint8) * 255
|
|
126
|
+
|
|
127
|
+
x = 0
|
|
128
|
+
for i in resized_images:
|
|
129
|
+
w = i.shape[1]
|
|
130
|
+
output_image[:i.shape[0], x:x + w] = i
|
|
131
|
+
x += w
|
|
132
|
+
|
|
133
|
+
return cv2.imwrite(output_image_file_path, output_image)
|
|
134
|
+
|
|
135
|
+
else:
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
except Exception as e: # noqa
|
|
139
|
+
return False
|
py3_kit/list.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def split(
|
|
5
|
+
data: list[Any],
|
|
6
|
+
num_parts: int | None = None,
|
|
7
|
+
part_size: int | None = None
|
|
8
|
+
) -> list[Any]:
|
|
9
|
+
if not data:
|
|
10
|
+
return []
|
|
11
|
+
|
|
12
|
+
if num_parts:
|
|
13
|
+
avg_size = len(data) // num_parts
|
|
14
|
+
remainder = len(data) % num_parts
|
|
15
|
+
|
|
16
|
+
result = []
|
|
17
|
+
result.extend(
|
|
18
|
+
[data[start:start + avg_size] for start in range(0, len(data) - avg_size - remainder, avg_size)])
|
|
19
|
+
result.append(data[len(data) - avg_size - remainder:])
|
|
20
|
+
return result
|
|
21
|
+
|
|
22
|
+
elif part_size:
|
|
23
|
+
return [data[i:i + part_size] for i in range(0, len(data), part_size)]
|
|
24
|
+
|
|
25
|
+
else:
|
|
26
|
+
raise ValueError(
|
|
27
|
+
f"Either num_parts: {num_parts!r} or part_size: {part_size!r} must be provided"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def flatten(data: list[Any]) -> list[Any]:
|
|
32
|
+
return sum((flatten(x) if isinstance(x, list) else [x] for x in data), [])
|
py3_kit/pdf.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from pypdf import PdfWriter
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def concat(
|
|
5
|
+
input_pdf_file_paths: list[str],
|
|
6
|
+
output_pdf_file_path: str
|
|
7
|
+
) -> bool:
|
|
8
|
+
try:
|
|
9
|
+
writer = PdfWriter()
|
|
10
|
+
|
|
11
|
+
for input_pdf_file_path in input_pdf_file_paths:
|
|
12
|
+
writer.append(input_pdf_file_path)
|
|
13
|
+
|
|
14
|
+
with open(output_pdf_file_path, "wb") as f:
|
|
15
|
+
writer.write(f)
|
|
16
|
+
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
except Exception: # noqa
|
|
20
|
+
return False
|
py3_kit/project.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
|
|
4
|
+
ALGO_TYPE = Literal[
|
|
5
|
+
"md5", "sha1", "sha224", "sha256", "sha384", "sha512", "blake2b", "blake2s", "sha3_224", "sha3_256", "sha3_384",
|
|
6
|
+
"sha3_512"
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def gen_data_id(
|
|
11
|
+
*args: Any,
|
|
12
|
+
keys: list | None = None, item: dict | None = None,
|
|
13
|
+
algo_type: ALGO_TYPE = "sha256",
|
|
14
|
+
encoding: str = "utf-8"
|
|
15
|
+
) -> str:
|
|
16
|
+
"""
|
|
17
|
+
>>> gen_data_id("123456")
|
|
18
|
+
'8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
*args:
|
|
22
|
+
keys:
|
|
23
|
+
item:
|
|
24
|
+
algo_type:
|
|
25
|
+
encoding:
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
m = hashlib.new(algo_type)
|
|
31
|
+
if args:
|
|
32
|
+
values = args
|
|
33
|
+
elif keys is not None and item is not None:
|
|
34
|
+
if isinstance(keys, list) and isinstance(item, dict):
|
|
35
|
+
values = [item[k] for k in keys if k in item]
|
|
36
|
+
else:
|
|
37
|
+
raise TypeError(
|
|
38
|
+
f"Invalid type for 'keys': "
|
|
39
|
+
f"Expected `list | None`, "
|
|
40
|
+
f"but got {type(keys).__name__} (value: {keys!r})\n"
|
|
41
|
+
f"Invalid type for 'item': "
|
|
42
|
+
f"Expected `dict | None`, "
|
|
43
|
+
f"but got {type(item).__name__} (value: {item!r})"
|
|
44
|
+
)
|
|
45
|
+
elif item is not None:
|
|
46
|
+
values = [item[k] for k in sorted(item.keys())]
|
|
47
|
+
else:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"Either args: {args!r} or keys: {keys!r} and item: {item!r} or item: {item!r} must be provided"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
data = list(map(lambda x: str(x), values))
|
|
53
|
+
|
|
54
|
+
for i in data:
|
|
55
|
+
m.update(i.encode(encoding))
|
|
56
|
+
|
|
57
|
+
data_id = m.hexdigest()
|
|
58
|
+
return data_id
|
py3_kit/sql.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import sqlparse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def format(sql: str, **kwargs: Any) -> str: # noqa
|
|
7
|
+
if not kwargs:
|
|
8
|
+
kwargs = dict(
|
|
9
|
+
reindent=True,
|
|
10
|
+
keyword_case="upper",
|
|
11
|
+
identifier_case="lower",
|
|
12
|
+
strip_comments=True
|
|
13
|
+
)
|
|
14
|
+
sql = sqlparse.format(sql, **kwargs)
|
|
15
|
+
return sql
|
py3_kit/validators.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import Any, TypeGuard
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def is_bool(obj: Any) -> TypeGuard[bool]:
|
|
5
|
+
return isinstance(obj, bool)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def is_int(obj: Any) -> TypeGuard[int]:
|
|
9
|
+
return isinstance(obj, int) and not isinstance(obj, bool)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_str(val: Any) -> TypeGuard[str]:
|
|
13
|
+
return isinstance(val, str)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_list(val: Any) -> TypeGuard[list[Any]]:
|
|
17
|
+
return isinstance(val, list)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_list_of[T](val: list[Any], ele_type: type[T]) -> TypeGuard[list[T]]:
|
|
21
|
+
return isinstance(val, list) and all(isinstance(i, ele_type) for i in val)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py3-kit
|
|
3
|
+
Version: 0.0.8
|
|
4
|
+
Summary: python3 kit
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: mathewgeola
|
|
7
|
+
Author-email: mathewgeola@gmail.com
|
|
8
|
+
Requires-Python: >=3.12,<3.15
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: arrow (>=1.4.0,<2.0.0)
|
|
14
|
+
Requires-Dist: opencv-python (>=4.12.0.88,<5.0.0.0)
|
|
15
|
+
Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
|
|
16
|
+
Requires-Dist: oss2 (>=2.19.1,<3.0.0)
|
|
17
|
+
Requires-Dist: pandas (>=2.3.3,<3.0.0)
|
|
18
|
+
Requires-Dist: pillow (>=12.1.0,<13.0.0)
|
|
19
|
+
Requires-Dist: pillow-avif-plugin (>=1.5.2,<2.0.0)
|
|
20
|
+
Requires-Dist: py3-database (>=0.0.1,<0.0.2)
|
|
21
|
+
Requires-Dist: py3-execute (>=0.0.5,<0.0.6)
|
|
22
|
+
Requires-Dist: py3-logger (>=0.0.7,<0.0.8)
|
|
23
|
+
Requires-Dist: py3-web (>=0.0.7,<0.0.8)
|
|
24
|
+
Requires-Dist: pyinstaller (>=6.17.0,<7.0.0)
|
|
25
|
+
Requires-Dist: pypdf (>=6.6.0,<7.0.0)
|
|
26
|
+
Requires-Dist: sqlparse (>=0.5.5,<0.6.0)
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# py3-kit
|
|
30
|
+
python3 kit
|
|
31
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
py3_kit/__datetime.py,sha256=MtkjvQv66TZh3g2ZEg2pFHUwU92oFwNujxkwmZZdsbo,535
|
|
2
|
+
py3_kit/__init__.py,sha256=LvOX65s7uPofdb3rXnCZFT5yfd9oZYrF0qh8RDa1pI0,689
|
|
3
|
+
py3_kit/__py3_execute__/__init__.py,sha256=VzgVjDvegZNaPuoBCr4_C8E7AiJH1IX70K5Qniypj7E,26
|
|
4
|
+
py3_kit/__py3_logger__/__init__.py,sha256=JmgoHRpIoPg0IgMRf-FAw4BCy9_NgbK7GembF_GP9sA,25
|
|
5
|
+
py3_kit/__py3_web__/__init__.py,sha256=yZ9iRIrSy6gGWuOhbIltQQem89XimM4xc6C-cPf1PTA,22
|
|
6
|
+
py3_kit/__string.py,sha256=o-mcgc3NTVJPdtvARkOCX8-8ekUqF_OyTmpyObgbgQU,535
|
|
7
|
+
py3_kit/_math.py,sha256=q19pDrndfysgAnU8KisgBUyr6SjiajV9j6v6Lbs1sFE,1030
|
|
8
|
+
py3_kit/_pandas.py,sha256=EXxLPcgcxAB87EsPo07ysEYQh0cbUR2Bo0ByhnfhsQ8,764
|
|
9
|
+
py3_kit/_types.py,sha256=ran8qyJDCf8gdwrZtDVDrQ8GFj5YtjEHJB2hQmBxV_Q,1561
|
|
10
|
+
py3_kit/assets/file/linux/7zz,sha256=oYYP3w1uw5Xg4nflIi6apIh0fbSqXIfR7IeaCRa6Cy8,2878000
|
|
11
|
+
py3_kit/assets/file/linux/7zzs,sha256=XvVvvvRKm2GANqkAOdSnCaGOCVDXk5ESEU9cQ2dMVCI,3759224
|
|
12
|
+
py3_kit/assets/file/linux/rar,sha256=ydystOHpMTvhHjcK23kbBjAt0LMUD63UBuY8V1gmPsU,798760
|
|
13
|
+
py3_kit/assets/file/linux/unrar,sha256=9BC4PJQvBkecCqIoIegsXLSErC1rP0FoR8LwdLSPFCM,441632
|
|
14
|
+
py3_kit/assets/file/mac/7zz,sha256=XC_TbwCmb3eH3PG63Zd9RKArUAY_5WeOHxn_ZHl0Mu0,5885696
|
|
15
|
+
py3_kit/assets/file/mac/rar,sha256=V6NgbJLR1mmLWWpeg_z77nFdfnLF-IR_kFAe2vP_XqE,693296
|
|
16
|
+
py3_kit/assets/file/mac/unrar,sha256=kR1GMUZ-Y-GoB2Q_YSs_-x-HrpHT1P5ISK9KhuVk4Qc,393992
|
|
17
|
+
py3_kit/assets/file/win/7zr.exe,sha256=J8vj1YBK0J6Qu8qpFtoNXDsL6UYtDg-2y1S-XtkDCHU,601088
|
|
18
|
+
py3_kit/assets/file/win/Rar.exe,sha256=cgpIHEQSHKuHDC5sVIMR2Qkt2X9nwpSVFoltI0vlbx4,822480
|
|
19
|
+
py3_kit/assets/file/win/UnRAR.exe,sha256=i8LtOnNL6ekMY_Os9q3Pbh8eZN28ZkPOIZDQQB2Y77U,548560
|
|
20
|
+
py3_kit/assets.py,sha256=WrrsPhrqX1i6lGVdbm_1Xgvvj-ViBbyWPJVmn7GFFRI,454
|
|
21
|
+
py3_kit/file.py,sha256=_xBsDqEB6imYg-P5dKRkgilFiRf9rtkWr4cFjAA9bQk,6765
|
|
22
|
+
py3_kit/image.py,sha256=TnDZTt-nTQV0NSqzWlAOTOLXMLRErDBkmdzJOfr9i1U,4809
|
|
23
|
+
py3_kit/list.py,sha256=ZBrvUsFcBIF41Juy_qInwuyZOOL8m4snXSLDS3pPe-A,892
|
|
24
|
+
py3_kit/pdf.py,sha256=zWrhf09N8DwbHOVBAzFqzW5voCXzDIKXcNEmHMxVFcw,425
|
|
25
|
+
py3_kit/project.py,sha256=tCwV6m3JZUmorBFW2kxVm5w4DWj_lUaMISjXMnhRT0Q,1604
|
|
26
|
+
py3_kit/sql.py,sha256=z6I7-AHuxJo6QeRrK4L7lnrT_VOn9tAwoquy-vDgAnY,332
|
|
27
|
+
py3_kit/validators.py,sha256=SWGVUv8ACrAMihVjVM7hSjTEQA0rNV3ScPFHsvQO3CY,528
|
|
28
|
+
py3_kit-0.0.8.dist-info/METADATA,sha256=5b7Ol89ZI5xao6uTtCG4uu2_SxqnRXBO65UCDcmAnM8,1041
|
|
29
|
+
py3_kit-0.0.8.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
30
|
+
py3_kit-0.0.8.dist-info/licenses/LICENSE,sha256=l9yqPB-E89lQPmUwybOix3aliW6hox6ZY14E0CnXK6c,1068
|
|
31
|
+
py3_kit-0.0.8.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mathewgeola
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|