vebplib 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.
Files changed (38) hide show
  1. vebplib-1.0.0/LICENSE.txt +21 -0
  2. vebplib-1.0.0/PKG-INFO +14 -0
  3. vebplib-1.0.0/README.md +1 -0
  4. vebplib-1.0.0/pyproject.toml +23 -0
  5. vebplib-1.0.0/scripts/build.py +4 -0
  6. vebplib-1.0.0/scripts/install.py +5 -0
  7. vebplib-1.0.0/scripts/upload.py +4 -0
  8. vebplib-1.0.0/setup.cfg +4 -0
  9. vebplib-1.0.0/test/message.py +5 -0
  10. vebplib-1.0.0/vebplib/__init__.py +11 -0
  11. vebplib-1.0.0/vebplib/args.py +12 -0
  12. vebplib-1.0.0/vebplib/color/__init__.py +203 -0
  13. vebplib-1.0.0/vebplib/color/_console/__init__.py +53 -0
  14. vebplib-1.0.0/vebplib/color/util.py +40 -0
  15. vebplib-1.0.0/vebplib/file/__init__.py +151 -0
  16. vebplib-1.0.0/vebplib/file/json.py +24 -0
  17. vebplib-1.0.0/vebplib/file/plist.py +24 -0
  18. vebplib-1.0.0/vebplib/func/__init__.py +80 -0
  19. vebplib-1.0.0/vebplib/func/injection.py +54 -0
  20. vebplib-1.0.0/vebplib/func/wrapper.py +16 -0
  21. vebplib-1.0.0/vebplib/launch.py +19 -0
  22. vebplib-1.0.0/vebplib/math.py +102 -0
  23. vebplib-1.0.0/vebplib/message.py +21 -0
  24. vebplib-1.0.0/vebplib/modulelib.py +92 -0
  25. vebplib-1.0.0/vebplib/output.py +22 -0
  26. vebplib-1.0.0/vebplib/path.py +30 -0
  27. vebplib-1.0.0/vebplib/system/__init__.py +164 -0
  28. vebplib-1.0.0/vebplib/text.py +64 -0
  29. vebplib-1.0.0/vebplib/types.py +4 -0
  30. vebplib-1.0.0/vebplib/venvs/__init__.py +2 -0
  31. vebplib-1.0.0/vebplib/venvs/path.py +12 -0
  32. vebplib-1.0.0/vebplib/venvs/version.py +12 -0
  33. vebplib-1.0.0/vebplib/zip.py +37 -0
  34. vebplib-1.0.0/vebplib.egg-info/PKG-INFO +14 -0
  35. vebplib-1.0.0/vebplib.egg-info/SOURCES.txt +36 -0
  36. vebplib-1.0.0/vebplib.egg-info/dependency_links.txt +1 -0
  37. vebplib-1.0.0/vebplib.egg-info/requires.txt +1 -0
  38. vebplib-1.0.0/vebplib.egg-info/top_level.txt +4 -0
@@ -0,0 +1,21 @@
1
+ GPL-3.0 License
2
+
3
+ Copyright (c) 2025 [Ndrzy]
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.
vebplib-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: vebplib
3
+ Version: 1.0.0
4
+ Summary: python lib
5
+ Author-email: ndrzy <dandan0019@outlook.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/meatdumplings0019/vebplib
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE.txt
11
+ Requires-Dist: colorama
12
+ Dynamic: license-file
13
+
14
+ # VebpLib
@@ -0,0 +1 @@
1
+ # VebpLib
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 48.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vebplib"
7
+ version = "1.0.0"
8
+ description = "python lib"
9
+ authors = [
10
+ { name = "ndrzy", email = "dandan0019@outlook.com" }
11
+ ]
12
+ readme = { file = "README.md", content-type = "text/markdown" }
13
+ requires-python = ">=3.10"
14
+ license = { text = "MIT" }
15
+ dependencies = [
16
+ 'colorama'
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/meatdumplings0019/vebplib"
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = [""]
@@ -0,0 +1,4 @@
1
+ import os
2
+
3
+ if __name__ == '__main__':
4
+ os.system('python -m build')
@@ -0,0 +1,5 @@
1
+ import os
2
+
3
+ if __name__ == '__main__':
4
+ os.system('pip install -r requirements.txt')
5
+ print("\nAll packages installed")
@@ -0,0 +1,4 @@
1
+ import os
2
+
3
+ if __name__ == '__main__':
4
+ os.system('twine.exe upload .\dist\*')
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ from vebplib.message import Message
2
+
3
+ msg = Message(1, "Error xlk")
4
+
5
+ print(msg)
@@ -0,0 +1,11 @@
1
+ import vebplib.color
2
+ import vebplib.file
3
+ import vebplib.system
4
+ import vebplib.venvs
5
+ import vebplib.text
6
+ import vebplib.args
7
+ import vebplib.math
8
+ import vebplib.message
9
+ import vebplib.path
10
+ import vebplib.func
11
+ import vebplib.types
@@ -0,0 +1,12 @@
1
+ import argparse
2
+
3
+ from typing import Optional
4
+
5
+ def parse_args(tokens: list[str], parser: argparse.ArgumentParser) -> Optional[argparse.Namespace]:
6
+ try:
7
+ return parser.parse_args(tokens)
8
+ except SystemExit:
9
+ return None
10
+ except Exception as e:
11
+ print(f"参数解析错误: {str(e)}")
12
+ return None
@@ -0,0 +1,203 @@
1
+ from vebplib.color._console import *
2
+ from vebplib.color.util import *
3
+
4
+
5
+ class MColor:
6
+ CONSOLE_COLOR = ForeWhite()
7
+
8
+ def __init__(self, r: int = 255, g: int = 255, b: int = 255, a: int = 255):
9
+ """
10
+ 增强版的Color, 支持rgb转hex, 可以直接作为pygame的Color
11
+ :param r: R
12
+ :param g: G
13
+ :param b: B
14
+ :param a: A
15
+ """
16
+ self._r, self._g, self._b, self._a = self.vali_rgba(r, g, b, a)
17
+
18
+ @staticmethod
19
+ def from_hex(hex_str: str = "#FFFFFF"):
20
+ """
21
+ 将16进制颜色代码转为MColor
22
+ :param hex_str: 16进制string
23
+ :return: new MColor
24
+ """
25
+ r, g, b, a = hex_to_rgb(hex_str)
26
+ return MColor(r, g, b, a)
27
+
28
+ @staticmethod
29
+ def vali_rgba(r, g, b, a=255):
30
+ return vali_rgba(r, g, b, a)
31
+
32
+ @property
33
+ def r(self) -> int:
34
+ return self._r
35
+
36
+ @r.setter
37
+ def r(self, value: int) -> None:
38
+ if 0 < value <= 255:
39
+ self._r = value
40
+
41
+ self.r = 255
42
+
43
+ @property
44
+ def float_r(self) -> float:
45
+ return self.r / 255.0
46
+
47
+ @property
48
+ def g(self) -> int:
49
+ return self._g
50
+
51
+ @g.setter
52
+ def g(self, value: int) -> None:
53
+ if 0 < value <= 255:
54
+ self._g = value
55
+ return
56
+
57
+ self.g = 255
58
+
59
+ @property
60
+ def float_g(self) -> float:
61
+ return self.g / 255.0
62
+
63
+ @property
64
+ def b(self) -> int:
65
+ return self._b
66
+
67
+ @b.setter
68
+ def b(self, value: int) -> None:
69
+ if 0 < value <= 255:
70
+ self._b = value
71
+ return
72
+
73
+ self.b = 255
74
+
75
+ @property
76
+ def float_b(self) -> float:
77
+ return self.b / 255.0
78
+
79
+ @property
80
+ def a(self) -> int:
81
+ return self._a
82
+
83
+ @a.setter
84
+ def a(self, value: int) -> None:
85
+ if 0 < value <= 255:
86
+ self._a = value
87
+ return
88
+
89
+ self.a = 255
90
+
91
+ @property
92
+ def float_a(self) -> float:
93
+ return self.a / 255.0
94
+
95
+ @property
96
+ def rgb(self) -> tuple[int, int, int]:
97
+ return self.r, self.g, self.b
98
+
99
+ @property
100
+ def float_rgb(self) -> tuple[float, float, float]:
101
+ return self.float_r, self.float_g, self.float_b
102
+
103
+ @property
104
+ def rgba(self) -> tuple[int, int, int, int]:
105
+ return self.rgb[0], self.rgb[1], self.rgb[2], self.a
106
+
107
+ @property
108
+ def float_rgba(self) -> tuple[float, float, float, float]:
109
+ return self.float_rgb[0], self.float_rgb[1], self.float_rgb[2], self.float_a
110
+
111
+ @property
112
+ def hex(self) -> str:
113
+ return rgb_to_hex(self.r, self.g, self.b)
114
+
115
+ def __iter__(self):
116
+ yield self.r
117
+ yield self.g
118
+ yield self.b
119
+ yield self.a
120
+
121
+ def __len__(self):
122
+ return 4
123
+
124
+ def __getitem__(self, item):
125
+ prop = self.r, self.g, self.b, self.a
126
+
127
+ return prop[item]
128
+
129
+ def __add__(self, other):
130
+ if isinstance(other, MColor):
131
+ r, g, b, a = other
132
+ return MColor(self.r + r, self.g + g, self.b + b, self.a + a)
133
+
134
+ raise TypeError(other)
135
+
136
+ def __sub__(self, other):
137
+ if isinstance(other, MColor):
138
+ r, g, b, a = other
139
+ return MColor(self.r - r, self.g - g, self.b - b, self.a - a)
140
+
141
+ raise TypeError(other)
142
+
143
+ def __repr__(self):
144
+ return f"[MColor r={self.r} g={self.g} b={self.b} a={self.a}]"
145
+
146
+ def __str__(self):
147
+ return f'{self.CONSOLE_COLOR}'
148
+
149
+
150
+ class Black(MColor):
151
+ CONSOLE_COLOR = ForeBlack()
152
+
153
+ def __init__(self, a: int = 255):
154
+ super().__init__(0, 0, 0, a)
155
+
156
+
157
+ class Red(MColor):
158
+ CONSOLE_COLOR = ForeRed()
159
+
160
+ def __init__(self, a: int = 255):
161
+ super().__init__(255, 0, 0, a)
162
+
163
+
164
+ class Green(MColor):
165
+ CONSOLE_COLOR = ForeGreen()
166
+
167
+ def __init__(self, a: int = 255):
168
+ super().__init__(0, 255, 0, a)
169
+
170
+
171
+ class Yellow(MColor):
172
+ CONSOLE_COLOR = ForeYellow()
173
+
174
+ def __init__(self, a: int = 255):
175
+ super().__init__(255, 255, 0, a)
176
+
177
+
178
+ class Blue(MColor):
179
+ CONSOLE_COLOR = ForeBlue()
180
+
181
+ def __init__(self, a: int = 255):
182
+ super().__init__(0, 0, 255, a)
183
+
184
+
185
+ class Magenta(MColor):
186
+ CONSOLE_COLOR = ForeMagenta()
187
+
188
+ def __init__(self, a: int = 255):
189
+ super().__init__(255, 0, 255, a)
190
+
191
+
192
+ class Cyan(MColor):
193
+ CONSOLE_COLOR = ForeCyan()
194
+
195
+ def __init__(self, a: int = 255):
196
+ super().__init__(0, 255, 255, a)
197
+
198
+
199
+ class White(MColor):
200
+ CONSOLE_COLOR = ForeWhite()
201
+
202
+ def __init__(self, a: int = 255):
203
+ super().__init__(255, 255, 255, a)
@@ -0,0 +1,53 @@
1
+ from colorama import Fore
2
+
3
+
4
+ class ConsoleColor:
5
+ def __init__(self, fore):
6
+ self._fore = fore
7
+
8
+ @property
9
+ def fore(self):
10
+ return self._fore
11
+
12
+ def __str__(self):
13
+ return self.fore
14
+
15
+
16
+ class ForeBlack(ConsoleColor):
17
+ def __init__(self):
18
+ super().__init__(Fore.BLACK)
19
+
20
+
21
+ class ForeRed(ConsoleColor):
22
+ def __init__(self):
23
+ super().__init__(Fore.RED)
24
+
25
+
26
+ class ForeGreen(ConsoleColor):
27
+ def __init__(self):
28
+ super().__init__(Fore.GREEN)
29
+
30
+
31
+ class ForeYellow(ConsoleColor):
32
+ def __init__(self):
33
+ super().__init__(Fore.YELLOW)
34
+
35
+
36
+ class ForeBlue(ConsoleColor):
37
+ def __init__(self):
38
+ super().__init__(Fore.BLUE)
39
+
40
+
41
+ class ForeMagenta(ConsoleColor):
42
+ def __init__(self):
43
+ super().__init__(Fore.MAGENTA)
44
+
45
+
46
+ class ForeCyan(ConsoleColor):
47
+ def __init__(self):
48
+ super().__init__(Fore.CYAN)
49
+
50
+
51
+ class ForeWhite(ConsoleColor):
52
+ def __init__(self):
53
+ super().__init__(Fore.WHITE)
@@ -0,0 +1,40 @@
1
+ def vali_rgba(r, g, b, a=255):
2
+ return (
3
+ min(255, max(0, r)),
4
+ min(255, max(0, g)),
5
+ min(255, max(0, b)),
6
+ min(255, max(0, a))
7
+ )
8
+
9
+ def hex_to_rgb(hex_str: str) -> tuple[int, int, int, int]:
10
+ r, g, b, a = 0, 0, 0, 255
11
+
12
+ hex_str = hex_str.lstrip('#')
13
+
14
+ if len(hex_str) not in (3, 4, 6, 8):
15
+ raise ValueError("十六进制颜色代码必须是3, 4, 6或8个字符")
16
+
17
+ if len(hex_str) == 3:
18
+ hex_str = ''.join([c * 2 for c in hex_str])
19
+ elif len(hex_str) == 4:
20
+ hex_str = ''.join([c * 2 for c in hex_str])
21
+ alpha_hex = hex_str[6:8]
22
+ a = int(alpha_hex, 16)
23
+ hex_str = hex_str[0:6]
24
+
25
+ if len(hex_str) == 8:
26
+ alpha_hex = hex_str[6:8]
27
+ a = int(alpha_hex, 16)
28
+ hex_str = hex_str[0:6]
29
+
30
+ try:
31
+ r = int(hex_str[0:2], 16)
32
+ g = int(hex_str[2:4], 16)
33
+ b = int(hex_str[4:6], 16)
34
+ except ValueError:
35
+ raise ValueError("无效的十六进制颜色代码")
36
+
37
+ return r, g, b, a
38
+
39
+ def rgb_to_hex(r, g, b) -> str:
40
+ return '#%02x%02x%02x' % vali_rgba(r, g, b)[0:3]
@@ -0,0 +1,151 @@
1
+ import os
2
+ import shutil
3
+ from pathlib import Path
4
+ from typing import Optional, Iterator
5
+
6
+ from vebplib.path import MPath
7
+ from vebplib.types import path_type
8
+
9
+
10
+ class Stream:
11
+ def __init__(self, path: path_type):
12
+ self._path = MPath.to_path(path)
13
+
14
+ @property
15
+ def name(self) -> str:
16
+ return self.path.name
17
+
18
+ @property
19
+ def path(self) -> Path:
20
+ return self._path
21
+
22
+ @property
23
+ def exists(self) -> bool:
24
+ return self.path.exists()
25
+
26
+ def copy(self, destination, ignore=None):
27
+ if ignore is None:
28
+ ignore = []
29
+
30
+ source = MPath.to_path(self.path)
31
+ destination = MPath.to_path(destination)
32
+
33
+ FolderStream(destination).create()
34
+
35
+ if not source.exists():
36
+ return False
37
+
38
+ if source.is_dir():
39
+ shutil.copytree(
40
+ source,
41
+ destination,
42
+ dirs_exist_ok=True,
43
+ ignore=shutil.ignore_patterns(*ignore),
44
+ )
45
+ else:
46
+ shutil.copy(
47
+ source,
48
+ destination,
49
+ )
50
+
51
+ return True
52
+
53
+ def delete(self):
54
+ source = MPath.to_path(self.path)
55
+
56
+ if source.is_dir():
57
+ shutil.rmtree(source, ignore_errors=True)
58
+ else:
59
+ source.unlink(missing_ok=True)
60
+
61
+ def __eq__(self, other):
62
+ return self.path == other.root
63
+
64
+ def __str__(self) -> str:
65
+ return str(self.path)
66
+
67
+ def __repr__(self) -> str:
68
+ return f"[{self.__class__.__name__} {self}]"
69
+
70
+
71
+ class FileStream(Stream):
72
+ @property
73
+ def suffix(self) -> str:
74
+ return self.path.suffix
75
+
76
+ def read(self) -> Optional[str]:
77
+ if not self.exists:
78
+ raise FileNotFoundError("File not found")
79
+
80
+ with open(self.path, "r", encoding="utf-8") as f:
81
+ return f.read()
82
+
83
+ def write(self, text: str):
84
+ if not self.exists:
85
+ raise FileNotFoundError("File not found")
86
+
87
+ with open(self.path, "w", encoding="utf-8") as f:
88
+ f.write(text)
89
+
90
+ def create(self, value: str = ""):
91
+ if self.exists:
92
+ return
93
+
94
+ with open(self.path, "w", encoding="utf-8") as f:
95
+ f.write(value)
96
+
97
+
98
+ class DirectoryInfo:
99
+ def __init__(self, path, folders, files) -> None:
100
+ self._path = os.path.normpath(path)
101
+ self._folders = folders
102
+ self._files = files
103
+
104
+ def __iter__(self) -> Iterator:
105
+ yield self._path
106
+ yield self._folders
107
+ yield self._files
108
+
109
+ def __repr__(self) -> str:
110
+ return f"<DirectoryInfo: {self._path}>"
111
+
112
+ @property
113
+ def path(self) -> str:
114
+ return self._path
115
+
116
+ @property
117
+ def folders(self) -> list["FolderStream"]:
118
+ return self._folders
119
+
120
+ @property
121
+ def files(self) -> list[FileStream]:
122
+ return self._files
123
+
124
+
125
+ class FolderStream(Stream):
126
+ def create(self):
127
+ if self.exists:
128
+ return self
129
+
130
+ self.path.mkdir(parents=True, exist_ok=True)
131
+
132
+ return self
133
+
134
+ def walk(self) -> Optional[DirectoryInfo]:
135
+ if not self.exists: return None
136
+
137
+ folders = []
138
+ files = []
139
+
140
+ try:
141
+ with os.scandir(self._path) as entries:
142
+ for entry in entries:
143
+ if entry.is_dir():
144
+ sub_folder = FolderStream(entry.path)
145
+ folders.append(sub_folder)
146
+ elif entry.is_file():
147
+ files.append(FileStream(entry.path))
148
+ except PermissionError:
149
+ pass
150
+
151
+ return DirectoryInfo(self._path, folders, files)
@@ -0,0 +1,24 @@
1
+ import json
2
+ from typing import Optional, Any
3
+
4
+ from rushlib.file import FileStream
5
+
6
+
7
+ class JsonStream(FileStream):
8
+ @property
9
+ def is_json(self) -> bool:
10
+ return self.suffix == ".json"
11
+
12
+ def read(self) -> Optional[dict[str, Any]]:
13
+ if not self.exists:
14
+ raise FileNotFoundError("File not found")
15
+
16
+ with open(self.path, mode='r', encoding='utf-8') as f:
17
+ return json.load(f)
18
+
19
+ def write(self, data: dict, indent=4) -> None:
20
+ if not self.exists:
21
+ raise FileNotFoundError("File not found")
22
+
23
+ with open(self._path, 'w', encoding="utf-8") as file:
24
+ json.dump(data, file, indent=indent, ensure_ascii=False)
@@ -0,0 +1,24 @@
1
+ import plistlib
2
+ from typing import Optional, Any
3
+
4
+ from vebplib.file import FileStream
5
+
6
+
7
+ class PlistStream(FileStream):
8
+ @property
9
+ def is_plist(self) -> bool:
10
+ return self.suffix == ".json"
11
+
12
+ def read(self) -> Optional[dict[str, Any]]:
13
+ if not self.exists:
14
+ raise FileNotFoundError("File not found")
15
+
16
+ with open(self.path, mode='rb', encoding='utf-8') as f:
17
+ return plistlib.load(f)
18
+
19
+ def write(self, data: dict, indent=4) -> None:
20
+ if not self.exists:
21
+ raise FileNotFoundError("File not found")
22
+
23
+ with open(self._path, 'wb', encoding="utf-8") as file:
24
+ plistlib.dump(data, file)
@@ -0,0 +1,80 @@
1
+ import inspect
2
+ from inspect import Parameter
3
+ from functools import wraps
4
+ from typing import Callable, Any
5
+
6
+
7
+ def smart_call(func: Callable, *args: Any, **kwargs: Any) -> Any:
8
+ """
9
+ 智能调用函数,根据目标函数的参数签名自动匹配传入的参数
10
+
11
+ :param func: 要调用的目标函数
12
+ :return: 目标函数的执行结果
13
+ """
14
+ try:
15
+ # 获取函数签名
16
+ sig = inspect.signature(func)
17
+ except (ValueError, TypeError):
18
+ # 无法获取签名时直接尝试调用
19
+ return func(*args, **kwargs)
20
+
21
+ # 准备参数绑定
22
+ bound_args = {}
23
+ params = sig.parameters
24
+
25
+ # 处理位置参数
26
+ args_iter = iter(args)
27
+ for name, param in params.items():
28
+ if param.kind in (Parameter.POSITIONAL_ONLY,
29
+ Parameter.POSITIONAL_OR_KEYWORD,
30
+ Parameter.KEYWORD_ONLY):
31
+ # 尝试从位置参数获取值
32
+ if args_iter:
33
+ try:
34
+ bound_args[name] = next(args_iter)
35
+ continue
36
+ except StopIteration:
37
+ pass
38
+
39
+ # 如果位置参数用完,尝试从关键字参数获取
40
+ if name in kwargs:
41
+ bound_args[name] = kwargs[name]
42
+ elif param.default is not Parameter.empty:
43
+ # 使用默认值
44
+ bound_args[name] = param.default
45
+ else:
46
+ # 必需参数缺失
47
+ raise TypeError(f"Missing required argument: {name}")
48
+
49
+ elif param.kind == Parameter.VAR_POSITIONAL:
50
+ # 处理 *args 参数
51
+ bound_args[name] = tuple(args_iter)
52
+ args_iter = None # 标记位置参数已耗尽
53
+
54
+ # 处理剩余的关键字参数
55
+ for name, param in params.items():
56
+ if param.kind == Parameter.VAR_KEYWORD:
57
+ # 处理 **kwargs 参数
58
+ bound_args[name] = {
59
+ k: v for k, v in kwargs.items()
60
+ if k not in bound_args
61
+ }
62
+ break
63
+ elif name not in bound_args and param.kind == Parameter.KEYWORD_ONLY:
64
+ # 处理仅关键字参数
65
+ if name in kwargs:
66
+ bound_args[name] = kwargs[name]
67
+ elif param.default is not Parameter.empty:
68
+ bound_args[name] = param.default
69
+ else:
70
+ raise TypeError(f"Missing required keyword argument: {name}")
71
+
72
+ return func(**bound_args)
73
+
74
+
75
+ def adapt_args(func):
76
+ @wraps(func)
77
+ def wrapper(*args, **kwargs):
78
+ return smart_call(func, *args, **kwargs)
79
+
80
+ return wrapper