AutoCython-zhang 2.3.2__tar.gz → 2.3.4__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.
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython/_version.py +1 -1
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython/compile.py +49 -47
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython/obfuscate.py +3 -9
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython_zhang.egg-info/PKG-INFO +5 -3
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython_zhang.egg-info/requires.txt +1 -1
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/PKG-INFO +5 -3
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/README.md +3 -1
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/pyproject.toml +1 -1
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython/AutoCython.py +0 -0
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython/__init__.py +0 -0
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython/run_tasks.py +0 -0
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython/tools.py +0 -0
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython_zhang.egg-info/SOURCES.txt +0 -0
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython_zhang.egg-info/dependency_links.txt +0 -0
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython_zhang.egg-info/entry_points.txt +0 -0
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython_zhang.egg-info/top_level.txt +0 -0
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/LICENSE +0 -0
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/MANIFEST.in +0 -0
- {autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/setup.cfg +0 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
1
|
import glob
|
|
2
|
+
import os
|
|
4
3
|
import shutil
|
|
5
|
-
import platform
|
|
6
|
-
import tempfile
|
|
7
4
|
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
8
7
|
|
|
9
8
|
from .obfuscate import obfuscate_source
|
|
10
9
|
|
|
10
|
+
_SUPPORTED_CYTHON_MAJOR = 3
|
|
11
|
+
|
|
11
12
|
|
|
12
13
|
def _strip_binary(path):
|
|
13
14
|
"""strip 符号表,失败静默"""
|
|
@@ -19,12 +20,41 @@ def _strip_binary(path):
|
|
|
19
20
|
except Exception:
|
|
20
21
|
pass
|
|
21
22
|
|
|
23
|
+
|
|
24
|
+
def _get_cython_major_version():
|
|
25
|
+
"""返回当前运行环境的 Cython 主版本号。"""
|
|
26
|
+
try:
|
|
27
|
+
import Cython
|
|
28
|
+
except ImportError as exc: # pragma: no cover
|
|
29
|
+
raise RuntimeError('编译 Python 源码需要先安装 Cython。') from exc
|
|
30
|
+
|
|
31
|
+
version = getattr(Cython, '__version__', '0')
|
|
32
|
+
major = str(version).split('.', 1)[0]
|
|
33
|
+
try:
|
|
34
|
+
return int(major)
|
|
35
|
+
except ValueError as exc: # pragma: no cover
|
|
36
|
+
raise RuntimeError(f'无法解析 Cython 版本: {version}') from exc
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _ensure_supported_cython():
|
|
40
|
+
"""阻断已知会破坏运行时注解的旧版 Cython。"""
|
|
41
|
+
major = _get_cython_major_version()
|
|
42
|
+
if major != _SUPPORTED_CYTHON_MAJOR:
|
|
43
|
+
raise RuntimeError(
|
|
44
|
+
'检测到不受支持的 Cython 版本。'
|
|
45
|
+
'AutoCython 要求 Cython>=3,<4;'
|
|
46
|
+
'因为 Cython<3 可能剥离编译产物中的运行时注解,'
|
|
47
|
+
'导致 Pydantic/FastAPI/dataclass 的字段发现失效。'
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
22
51
|
def get_platform_extension() -> str:
|
|
23
52
|
"""返回当前平台的扩展名"""
|
|
24
53
|
if sys.platform.startswith('win'):
|
|
25
54
|
return '.pyd'
|
|
26
55
|
return '.so'
|
|
27
56
|
|
|
57
|
+
|
|
28
58
|
def compile_to_binary(file_path: str, del_source=False, obfuscate=True, obfuscate_seed=None):
|
|
29
59
|
"""
|
|
30
60
|
将指定的 Python 文件(.py)通过 Cython 编译为二进制扩展文件
|
|
@@ -35,39 +65,30 @@ def compile_to_binary(file_path: str, del_source=False, obfuscate=True, obfuscat
|
|
|
35
65
|
:param obfuscate_seed: 混淆随机种子(None 表示不固定)
|
|
36
66
|
:return: 生成的二进制文件路径(与输入保持相同的路径类型)
|
|
37
67
|
"""
|
|
38
|
-
|
|
39
|
-
is_absolute = os.path.isabs(file_path)
|
|
68
|
+
_ensure_supported_cython()
|
|
40
69
|
|
|
41
|
-
|
|
70
|
+
is_absolute = os.path.isabs(file_path)
|
|
42
71
|
abs_file_path = os.path.abspath(file_path)
|
|
43
72
|
|
|
44
73
|
if not os.path.isfile(abs_file_path):
|
|
45
74
|
raise FileNotFoundError(f"FileNotFoundError: {file_path}.")
|
|
46
75
|
|
|
47
|
-
# 获取文件名和目录
|
|
48
76
|
file_name = os.path.basename(abs_file_path)
|
|
49
77
|
module_name, ext = os.path.splitext(file_name)
|
|
50
|
-
source_dir = os.path.dirname(abs_file_path)
|
|
78
|
+
source_dir = os.path.dirname(abs_file_path)
|
|
51
79
|
|
|
52
|
-
if ext !=
|
|
80
|
+
if ext != '.py':
|
|
53
81
|
raise ValueError(f"ValueError: The file {file_path} is not a valid Python file (.py)!")
|
|
54
82
|
|
|
55
|
-
# 连字符文件名转下划线(Cython 模块名不允许连字符)
|
|
56
83
|
safe_module_name = module_name.replace('-', '_')
|
|
57
84
|
safe_file_name = safe_module_name + ext
|
|
58
|
-
|
|
59
|
-
# 获取平台特定扩展名
|
|
60
85
|
target_ext = get_platform_extension()
|
|
61
|
-
|
|
62
|
-
# 创建临时工作目录
|
|
63
86
|
temp_dir = tempfile.mkdtemp()
|
|
64
87
|
|
|
65
88
|
try:
|
|
66
|
-
# 将目标文件复制到临时目录(使用安全文件名)
|
|
67
89
|
temp_file_path = os.path.join(temp_dir, safe_file_name)
|
|
68
90
|
shutil.copy2(abs_file_path, temp_file_path)
|
|
69
91
|
|
|
70
|
-
# 混淆源码(失败则显式抛错,避免静默降级)
|
|
71
92
|
if obfuscate:
|
|
72
93
|
try:
|
|
73
94
|
with open(temp_file_path, 'r', encoding='utf-8') as f:
|
|
@@ -78,12 +99,10 @@ def compile_to_binary(file_path: str, del_source=False, obfuscate=True, obfuscat
|
|
|
78
99
|
except Exception as exc:
|
|
79
100
|
raise RuntimeError(f"Obfuscation failed for {file_path}: {exc}") from exc
|
|
80
101
|
|
|
81
|
-
# 创建临时的 setup.py 文件
|
|
82
102
|
setup_code = f"""
|
|
83
103
|
from setuptools import setup
|
|
84
104
|
from Cython.Build import cythonize
|
|
85
105
|
|
|
86
|
-
# 编译器指令
|
|
87
106
|
compiler_directives = {{
|
|
88
107
|
'language_level': '3',
|
|
89
108
|
'annotation_typing': False,
|
|
@@ -101,19 +120,18 @@ setup(
|
|
|
101
120
|
)
|
|
102
121
|
)
|
|
103
122
|
"""
|
|
104
|
-
setup_path = os.path.join(temp_dir,
|
|
105
|
-
with open(setup_path,
|
|
123
|
+
setup_path = os.path.join(temp_dir, 'setup.py')
|
|
124
|
+
with open(setup_path, 'w', encoding='utf-8') as f:
|
|
106
125
|
f.write(setup_code)
|
|
107
126
|
|
|
108
|
-
|
|
109
|
-
command = [sys.executable, "setup.py", "build_ext", "--inplace"]
|
|
127
|
+
command = [sys.executable, 'setup.py', 'build_ext', '--inplace']
|
|
110
128
|
try:
|
|
111
129
|
result = subprocess.run(
|
|
112
130
|
command,
|
|
113
131
|
cwd=temp_dir,
|
|
114
132
|
stdout=subprocess.PIPE,
|
|
115
133
|
stderr=subprocess.PIPE,
|
|
116
|
-
timeout=300
|
|
134
|
+
timeout=300,
|
|
117
135
|
)
|
|
118
136
|
except subprocess.TimeoutExpired:
|
|
119
137
|
raise RuntimeError(f"Compilation timed out after 300s: {file_path}")
|
|
@@ -122,57 +140,41 @@ setup(
|
|
|
122
140
|
error_msg = result.stderr.decode('utf-8', errors='replace')
|
|
123
141
|
raise RuntimeError(f"Compilation failed: {error_msg}")
|
|
124
142
|
|
|
125
|
-
# 查找生成的二进制文件
|
|
126
143
|
pattern = os.path.join(temp_dir, f"{safe_module_name}*{target_ext}")
|
|
127
144
|
matches = glob.glob(pattern)
|
|
128
|
-
|
|
129
145
|
if not matches:
|
|
130
146
|
pattern = os.path.join(temp_dir, f"*{safe_module_name}*{target_ext}")
|
|
131
147
|
matches = glob.glob(pattern)
|
|
132
148
|
|
|
133
149
|
if not matches:
|
|
134
|
-
raise FileNotFoundError(
|
|
150
|
+
raise FileNotFoundError(
|
|
151
|
+
f"FileNotFoundError: The file {file_path} is not a valid Python file (.py)! "
|
|
152
|
+
f"Generated file {target_ext} not found, in {temp_dir} possible file: {os.listdir(temp_dir)}"
|
|
153
|
+
)
|
|
135
154
|
|
|
136
|
-
# 取最新生成的文件
|
|
137
155
|
generated_file = max(matches, key=os.path.getmtime)
|
|
138
|
-
|
|
139
|
-
# 获取源文件的目录(使用原始路径类型)
|
|
140
|
-
if is_absolute:
|
|
141
|
-
output_dir = source_dir
|
|
142
|
-
else:
|
|
143
|
-
# 如果输入是相对路径,保持相对路径
|
|
144
|
-
output_dir = os.path.dirname(file_path) or '.'
|
|
145
|
-
|
|
146
|
-
# 创建目标目录(如果不存在)
|
|
156
|
+
output_dir = source_dir if is_absolute else (os.path.dirname(file_path) or '.')
|
|
147
157
|
if output_dir and not os.path.exists(output_dir):
|
|
148
158
|
os.makedirs(output_dir, exist_ok=True)
|
|
149
159
|
|
|
150
|
-
# 目标文件路径(保持原始路径类型)
|
|
151
160
|
output_file_name = os.path.basename(generated_file)
|
|
152
161
|
output_path = os.path.join(output_dir, output_file_name)
|
|
153
|
-
|
|
154
|
-
# 移动文件(先删除已存在的目标)
|
|
155
162
|
if os.path.exists(output_path):
|
|
156
163
|
os.remove(output_path)
|
|
157
164
|
shutil.move(generated_file, output_path)
|
|
158
165
|
|
|
159
|
-
# strip 符号表
|
|
160
166
|
_strip_binary(output_path)
|
|
161
167
|
|
|
162
168
|
if del_source:
|
|
163
169
|
os.remove(file_path)
|
|
164
170
|
|
|
165
|
-
# 返回与输入相同类型的路径
|
|
166
171
|
return output_path
|
|
167
172
|
finally:
|
|
168
|
-
# 清理临时目录
|
|
169
173
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
170
174
|
|
|
171
|
-
# 测试函数
|
|
172
|
-
if __name__ == "__main__": # pragma: no cover
|
|
173
|
-
# 替换为你的 Python 文件路径
|
|
174
|
-
target_file = "test/example.py" # 请确保文件路径正确
|
|
175
175
|
|
|
176
|
+
if __name__ == '__main__': # pragma: no cover
|
|
177
|
+
target_file = 'test/example.py'
|
|
176
178
|
try:
|
|
177
179
|
output_file = compile_to_binary(target_file)
|
|
178
180
|
print(output_file)
|
|
@@ -75,8 +75,9 @@ class _DocstringRemover(ast.NodeTransformer):
|
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
class _AnnotationRemover(ast.NodeTransformer):
|
|
78
|
-
"""
|
|
79
|
-
|
|
78
|
+
"""清除非类体内的变量注解。
|
|
79
|
+
保留函数参数/返回值注解和类体内注解赋值,
|
|
80
|
+
因为 FastAPI/Pydantic/dataclass/attrs 等框架在运行时依赖 __annotations__。"""
|
|
80
81
|
|
|
81
82
|
def __init__(self):
|
|
82
83
|
self._scope_stack = ['module']
|
|
@@ -85,13 +86,6 @@ class _AnnotationRemover(ast.NodeTransformer):
|
|
|
85
86
|
self._scope_stack.append('function')
|
|
86
87
|
self.generic_visit(node)
|
|
87
88
|
self._scope_stack.pop()
|
|
88
|
-
node.returns = None
|
|
89
|
-
for arg in node.args.args + node.args.posonlyargs + node.args.kwonlyargs:
|
|
90
|
-
arg.annotation = None
|
|
91
|
-
if node.args.vararg:
|
|
92
|
-
node.args.vararg.annotation = None
|
|
93
|
-
if node.args.kwarg:
|
|
94
|
-
node.args.kwarg.annotation = None
|
|
95
89
|
return node
|
|
96
90
|
|
|
97
91
|
visit_AsyncFunctionDef = visit_FunctionDef
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: AutoCython-zhang
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.4
|
|
4
4
|
Summary: 自动Cython,使用Cython批量编译.py文件为.pyd文件!
|
|
5
5
|
Author-email: zhang_gavin <qq814608@163.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -14,7 +14,7 @@ Requires-Python: >=3.9
|
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: setuptools
|
|
17
|
-
Requires-Dist: cython
|
|
17
|
+
Requires-Dist: cython<4,>=3
|
|
18
18
|
Requires-Dist: rich
|
|
19
19
|
Dynamic: license-file
|
|
20
20
|
|
|
@@ -47,11 +47,13 @@ AutoCython 是一个 Python 源码保护工具,通过 Cython 编译 + AST 混
|
|
|
47
47
|
pip install AutoCython-zhang
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
> 当前编译链要求 `Cython>=3,<4`。该约束用于避免旧版 Cython 在编译后破坏 `Pydantic` / `FastAPI` / `dataclass` 等依赖运行时注解的框架行为。
|
|
51
|
+
|
|
50
52
|
### 依赖
|
|
51
53
|
|
|
52
54
|
| 包 | 用途 |
|
|
53
55
|
|---|------|
|
|
54
|
-
| `cython` | Python → C
|
|
56
|
+
| `cython>=3,<4` | Python → C 编译核心;确保运行时注解不被旧版编译链破坏 |
|
|
55
57
|
| `setuptools` | 构建扩展模块 |
|
|
56
58
|
| `rich` | 终端实时进度面板 |
|
|
57
59
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: AutoCython-zhang
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.4
|
|
4
4
|
Summary: 自动Cython,使用Cython批量编译.py文件为.pyd文件!
|
|
5
5
|
Author-email: zhang_gavin <qq814608@163.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -14,7 +14,7 @@ Requires-Python: >=3.9
|
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: setuptools
|
|
17
|
-
Requires-Dist: cython
|
|
17
|
+
Requires-Dist: cython<4,>=3
|
|
18
18
|
Requires-Dist: rich
|
|
19
19
|
Dynamic: license-file
|
|
20
20
|
|
|
@@ -47,11 +47,13 @@ AutoCython 是一个 Python 源码保护工具,通过 Cython 编译 + AST 混
|
|
|
47
47
|
pip install AutoCython-zhang
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
> 当前编译链要求 `Cython>=3,<4`。该约束用于避免旧版 Cython 在编译后破坏 `Pydantic` / `FastAPI` / `dataclass` 等依赖运行时注解的框架行为。
|
|
51
|
+
|
|
50
52
|
### 依赖
|
|
51
53
|
|
|
52
54
|
| 包 | 用途 |
|
|
53
55
|
|---|------|
|
|
54
|
-
| `cython` | Python → C
|
|
56
|
+
| `cython>=3,<4` | Python → C 编译核心;确保运行时注解不被旧版编译链破坏 |
|
|
55
57
|
| `setuptools` | 构建扩展模块 |
|
|
56
58
|
| `rich` | 终端实时进度面板 |
|
|
57
59
|
|
|
@@ -27,11 +27,13 @@ AutoCython 是一个 Python 源码保护工具,通过 Cython 编译 + AST 混
|
|
|
27
27
|
pip install AutoCython-zhang
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
> 当前编译链要求 `Cython>=3,<4`。该约束用于避免旧版 Cython 在编译后破坏 `Pydantic` / `FastAPI` / `dataclass` 等依赖运行时注解的框架行为。
|
|
31
|
+
|
|
30
32
|
### 依赖
|
|
31
33
|
|
|
32
34
|
| 包 | 用途 |
|
|
33
35
|
|---|------|
|
|
34
|
-
| `cython` | Python → C
|
|
36
|
+
| `cython>=3,<4` | Python → C 编译核心;确保运行时注解不被旧版编译链破坏 |
|
|
35
37
|
| `setuptools` | 构建扩展模块 |
|
|
36
38
|
| `rich` | 终端实时进度面板 |
|
|
37
39
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython_zhang.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{autocython_zhang-2.3.2 → autocython_zhang-2.3.4}/AutoCython_zhang.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|