AutoCython-zhang 2.3.6__tar.gz → 2.3.8__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 (19) hide show
  1. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython/_version.py +1 -1
  2. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython/obfuscate.py +198 -80
  3. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython_zhang.egg-info/PKG-INFO +7 -5
  4. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/PKG-INFO +7 -5
  5. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/README.md +6 -4
  6. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/pyproject.toml +1 -1
  7. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython/AutoCython.py +0 -0
  8. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython/__init__.py +0 -0
  9. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython/compile.py +0 -0
  10. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython/run_tasks.py +0 -0
  11. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython/tools.py +0 -0
  12. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython_zhang.egg-info/SOURCES.txt +0 -0
  13. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython_zhang.egg-info/dependency_links.txt +0 -0
  14. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython_zhang.egg-info/entry_points.txt +0 -0
  15. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython_zhang.egg-info/requires.txt +0 -0
  16. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/AutoCython_zhang.egg-info/top_level.txt +0 -0
  17. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/LICENSE +0 -0
  18. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/MANIFEST.in +0 -0
  19. {autocython_zhang-2.3.6 → autocython_zhang-2.3.8}/setup.cfg +0 -0
@@ -2,4 +2,4 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  """版本号模块 - 单一版本号来源"""
4
4
 
5
- __version__ = "2.3.6"
5
+ __version__ = "2.3.8"
@@ -1,4 +1,8 @@
1
- """AST 混淆模块:去 docstring、去 annotation、局部变量重命名、字符串加密、常量折叠、控制流平坦化、虚假分支"""
1
+ """AST 混淆模块:去 docstring、局部变量重命名、字符串加密、常量折叠、控制流平坦化、虚假分支。
2
+
3
+ 注解(annotation)默认完整保留,以兼容 FastAPI/Pydantic/dataclass/attrs 等依赖运行时
4
+ ``__annotations__`` 的框架;字符串与常量混淆会跳过 annotation 子树。
5
+ """
2
6
  import ast
3
7
  import copy
4
8
  import hashlib
@@ -6,6 +10,16 @@ import random
6
10
 
7
11
  _UNSAFE_CALLS = frozenset({'globals', 'locals', 'eval', 'exec', 'vars'})
8
12
  _PATTERN_SKIP_ATTR = '_autocython_skip_literal_obf'
13
+ _SCOPE_BOUNDARY_NODES = (
14
+ ast.FunctionDef,
15
+ ast.AsyncFunctionDef,
16
+ ast.ClassDef,
17
+ ast.Lambda,
18
+ ast.ListComp,
19
+ ast.SetComp,
20
+ ast.DictComp,
21
+ ast.GeneratorExp,
22
+ )
9
23
 
10
24
 
11
25
  def _has_unsafe_call(node):
@@ -21,14 +35,18 @@ def _has_unsafe_call(node):
21
35
 
22
36
 
23
37
  def _walk_no_nested_scope(body):
24
- """遍历 body 中的节点,不穿透嵌套函数/类定义"""
38
+ """遍历 body 中的节点,不穿透嵌套词法作用域。
39
+
40
+ Python 3 中 lambda 与各类 comprehension 也会创建独立词法作用域。
41
+ 局部变量重命名若穿透这些节点,容易把 ``lambda e: ...`` 的参数
42
+ 与外层 ``for e in ...`` 的重命名混在一起,生成
43
+ ``free variable ... referenced before assignment`` 一类运行时错误。
44
+ """
25
45
  for node in body:
26
46
  yield node
27
- if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
47
+ if isinstance(node, _SCOPE_BOUNDARY_NODES):
28
48
  continue
29
49
  for child in ast.iter_child_nodes(node):
30
- if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
31
- continue
32
50
  yield from _walk_no_nested_scope([child])
33
51
 
34
52
 
@@ -49,6 +67,34 @@ def _mark_pattern_constants(pattern):
49
67
  setattr(child, _PATTERN_SKIP_ATTR, True)
50
68
 
51
69
 
70
+ def _mark_annotation_constants(tree):
71
+ """标记 annotation 子树内常量,避免字符串加密/常量折叠改变运行时注解语义。"""
72
+
73
+ def mark(node):
74
+ if node is None:
75
+ return
76
+ for child in ast.walk(node):
77
+ if isinstance(child, ast.Constant):
78
+ setattr(child, _PATTERN_SKIP_ATTR, True)
79
+
80
+ def mark_arg_annotations(args):
81
+ all_args = args.posonlyargs + args.args + args.kwonlyargs
82
+ if args.vararg:
83
+ all_args.append(args.vararg)
84
+ if args.kwarg:
85
+ all_args.append(args.kwarg)
86
+ for arg in all_args:
87
+ mark(arg.annotation)
88
+
89
+ for node in ast.walk(tree):
90
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
91
+ mark_arg_annotations(node.args)
92
+ mark(node.returns)
93
+ elif isinstance(node, ast.AnnAssign):
94
+ mark(node.annotation)
95
+ return tree
96
+
97
+
52
98
  class _DocstringRemover(ast.NodeTransformer):
53
99
  """移除 module/class/function 首部的 docstring"""
54
100
 
@@ -74,80 +120,156 @@ class _DocstringRemover(ast.NodeTransformer):
74
120
  return self._strip_docstring(node)
75
121
 
76
122
 
77
- class _AnnotationRemover(ast.NodeTransformer):
78
- """清除非类体内的变量注解。
79
- 保留函数参数/返回值注解和类体内注解赋值,
80
- 因为 FastAPI/Pydantic/dataclass/attrs 等框架在运行时依赖 __annotations__。"""
123
+ def _collect_names(nodes, ctx_type, *, skip_nested=True):
124
+ """收集一组节点中的 Name;默认不穿透新的词法作用域。"""
125
+ names = set()
126
+ walker = _walk_no_nested_scope(nodes) if skip_nested else ast.walk(ast.Module(body=list(nodes), type_ignores=[]))
127
+ for child in walker:
128
+ if isinstance(child, ast.Name) and isinstance(child.ctx, ctx_type):
129
+ names.add(child.id)
130
+ return names
131
+
132
+
133
+ def _collect_argument_names(args):
134
+ """收集函数/lambda 参数名。"""
135
+ names = set()
136
+ for arg in args.args + args.posonlyargs + args.kwonlyargs:
137
+ names.add(arg.arg)
138
+ if args.vararg:
139
+ names.add(args.vararg.arg)
140
+ if args.kwarg:
141
+ names.add(args.kwarg.arg)
142
+ return names
143
+
144
+
145
+ def _collect_argument_expression_nodes(args):
146
+ """收集默认值与注解表达式,它们在外层作用域求值。"""
147
+ nodes = []
148
+ nodes.extend(args.defaults)
149
+ nodes.extend(default for default in args.kw_defaults if default is not None)
150
+ all_args = args.posonlyargs + args.args + args.kwonlyargs
151
+ if args.vararg:
152
+ all_args.append(args.vararg)
153
+ if args.kwarg:
154
+ all_args.append(args.kwarg)
155
+ for arg in all_args:
156
+ if arg.annotation is not None:
157
+ nodes.append(arg.annotation)
158
+ return nodes
159
+
160
+
161
+ def _collect_nonlocal_global_names(body):
162
+ """收集当前作用域直接声明的 nonlocal/global 名称。"""
163
+ nonlocal_names = set()
164
+ global_names = set()
165
+ for child in _walk_no_nested_scope(body):
166
+ if isinstance(child, ast.Nonlocal):
167
+ nonlocal_names.update(child.names)
168
+ elif isinstance(child, ast.Global):
169
+ global_names.update(child.names)
170
+ return nonlocal_names, global_names
171
+
172
+
173
+ def _collect_direct_bound_names(body):
174
+ """收集当前作用域直接绑定的名称,不穿透嵌套作用域。"""
175
+ bound = _collect_names(body, ast.Store)
176
+ for child in _walk_no_nested_scope(body):
177
+ if isinstance(child, ast.ExceptHandler) and child.name:
178
+ bound.add(child.name)
179
+ elif isinstance(child, (ast.Import, ast.ImportFrom)):
180
+ for alias in child.names:
181
+ bound.add(alias.asname or alias.name.split('.', 1)[0])
182
+ return bound
183
+
184
+
185
+ def _collect_load_names_no_nested(nodes):
186
+ """收集 Load 名称,不穿透嵌套作用域。"""
187
+ return _collect_names(nodes, ast.Load)
188
+
189
+
190
+ def _collect_nested_scope_nodes(body):
191
+ """收集 body 中的直接/间接嵌套词法作用域节点。"""
192
+ nested = []
193
+ for node in body:
194
+ if isinstance(node, _SCOPE_BOUNDARY_NODES):
195
+ nested.append(node)
196
+ continue
197
+ for child in ast.iter_child_nodes(node):
198
+ if isinstance(child, _SCOPE_BOUNDARY_NODES):
199
+ nested.append(child)
200
+ else:
201
+ nested.extend(_collect_nested_scope_nodes([child]))
202
+ return nested
81
203
 
82
- def __init__(self):
83
- self._scope_stack = ['module']
84
204
 
85
- def visit_FunctionDef(self, node):
86
- self._scope_stack.append('function')
87
- self.generic_visit(node)
88
- self._scope_stack.pop()
89
- return node
205
+ def _collect_function_like_free_refs(node, local_names):
206
+ """收集嵌套函数/lambda 中引用的外层变量名。"""
207
+ body = node.body if isinstance(node.body, list) else [node.body]
208
+ param_names = _collect_argument_names(node.args)
209
+ nonlocal_names, global_names = _collect_nonlocal_global_names(body)
210
+ nested_locals = _collect_direct_bound_names(body)
211
+ nested_locals.update(param_names)
212
+ nested_locals -= nonlocal_names
90
213
 
91
- visit_AsyncFunctionDef = visit_FunctionDef
214
+ free = set()
215
+ outer_expr_nodes = _collect_argument_expression_nodes(node.args)
216
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
217
+ outer_expr_nodes.extend(node.decorator_list)
218
+ if node.returns is not None:
219
+ outer_expr_nodes.append(node.returns)
220
+ free.update(_collect_load_names_no_nested(outer_expr_nodes) & local_names)
221
+
222
+ for name in _collect_load_names_no_nested(body):
223
+ if name in local_names and name not in nested_locals and name not in global_names:
224
+ free.add(name)
225
+
226
+ # ``nonlocal`` 声明的变量即使只被 Store(如 current_tokens = 0)使用,
227
+ # 也必须视为外层闭包引用。若外层变量被重命名而 nonlocal 声明仍保留原名,
228
+ # Cython 会在编译期报 ``no binding for nonlocal ... found``。
229
+ free.update(nonlocal_names & local_names)
230
+
231
+ free.update(_collect_nested_free_refs(body, local_names))
232
+ return free
92
233
 
93
- def visit_ClassDef(self, node):
94
- self._scope_stack.append('class')
95
- self.generic_visit(node)
96
- self._scope_stack.pop()
97
- if not node.body:
98
- node.body.append(ast.Pass())
99
- return node
100
234
 
101
- def visit_AnnAssign(self, node):
102
- self.generic_visit(node)
103
- # 类体内的注解赋值必须保留(Pydantic/dataclass 等框架依赖 __annotations__)
104
- if self._scope_stack[-1] == 'class':
105
- return node
106
- if node.value is not None:
107
- return ast.Assign(targets=[node.target], value=node.value,
108
- lineno=node.lineno, col_offset=node.col_offset)
109
- return None # 纯注解无赋值,直接删除
235
+ def _collect_class_free_refs(node, local_names):
236
+ """收集嵌套类体中直接引用的外层变量名。"""
237
+ free = set()
238
+ outer_expr_nodes = list(node.decorator_list)
239
+ outer_expr_nodes.extend(node.bases)
240
+ outer_expr_nodes.extend(keyword.value for keyword in node.keywords)
241
+ free.update(_collect_load_names_no_nested(outer_expr_nodes) & local_names)
242
+
243
+ for name in _collect_load_names_no_nested(node.body):
244
+ if name in local_names:
245
+ free.add(name)
246
+ free.update(_collect_nested_free_refs(node.body, local_names))
247
+ return free
248
+
249
+
250
+ def _collect_comprehension_free_refs(node, local_names):
251
+ """保守收集 comprehension 中对外层变量的引用。
252
+
253
+ comprehension 是隐式函数作用域;为避免 Cython/CPython 闭包细节差异,
254
+ 只要其中出现外层局部名,就将该外层名排除出重命名集合。
255
+ """
256
+ free = set()
257
+ for child in ast.walk(node):
258
+ if isinstance(child, ast.Name) and isinstance(child.ctx, ast.Load) and child.id in local_names:
259
+ free.add(child.id)
260
+ return free
110
261
 
111
262
 
112
263
  def _collect_nested_free_refs(body, local_names):
113
- """收集嵌套函数/类中引用的外层变量名(闭包变量)"""
264
+ """收集嵌套词法作用域中引用的外层变量名(闭包变量)"""
114
265
  free = set()
115
- for node in body:
116
- if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
117
- # 嵌套函数自己的局部名
118
- nested_locals = set()
119
- # nonlocal/global 声明的变量不是局部变量
120
- nonlocal_names = set()
121
- for child in _walk_no_nested_scope(node.body):
122
- if isinstance(child, (ast.Nonlocal, ast.Global)):
123
- nonlocal_names.update(child.names)
124
- for arg in node.args.args + node.args.posonlyargs + node.args.kwonlyargs:
125
- nested_locals.add(arg.arg)
126
- if node.args.vararg:
127
- nested_locals.add(node.args.vararg.arg)
128
- if node.args.kwarg:
129
- nested_locals.add(node.args.kwarg.arg)
130
- for child in _walk_no_nested_scope(node.body):
131
- if isinstance(child, ast.Name) and isinstance(child.ctx, ast.Store):
132
- if child.id not in nonlocal_names:
133
- nested_locals.add(child.id)
134
- # 嵌套函数中读取的、属于外层 local_names 且不被自身遮蔽的名字
135
- for child in ast.walk(node):
136
- if isinstance(child, ast.Name) and child.id in local_names and child.id not in nested_locals:
137
- free.add(child.id)
266
+ for node in _collect_nested_scope_nodes(body):
267
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)):
268
+ free.update(_collect_function_like_free_refs(node, local_names))
138
269
  elif isinstance(node, ast.ClassDef):
139
- # 类体中直接引用的外层变量
140
- for child in _walk_no_nested_scope(node.body):
141
- if isinstance(child, ast.Name) and child.id in local_names and isinstance(child.ctx, ast.Load):
142
- free.add(child.id)
143
- free |= _collect_nested_free_refs(node.body, local_names)
144
- else:
145
- # 递归搜索所有子节点中的嵌套函数/类(穿透 Try/With/For/If 等)
146
- for child in ast.walk(node):
147
- if child is node:
148
- continue
149
- if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
150
- free |= _collect_nested_free_refs([child], local_names)
270
+ free.update(_collect_class_free_refs(node, local_names))
271
+ elif isinstance(node, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)):
272
+ free.update(_collect_comprehension_free_refs(node, local_names))
151
273
  return free
152
274
 
153
275
 
@@ -167,26 +289,20 @@ class _LocalVarRenamer(ast.NodeTransformer):
167
289
 
168
290
  # 收集参数名(不重命名)
169
291
  param_names = set()
170
- for arg in node.args.args + node.args.posonlyargs + node.args.kwonlyargs:
171
- param_names.add(arg.arg)
172
- if node.args.vararg:
173
- param_names.add(node.args.vararg.arg)
174
- if node.args.kwarg:
175
- param_names.add(node.args.kwarg.arg)
292
+ param_names.update(_collect_argument_names(node.args))
176
293
 
177
294
  # 收集局部赋值目标(不穿透嵌套作用域)
178
295
  local_names = set()
179
296
  # 排除 nonlocal/global 声明的变量
180
- nonlocal_names = set()
297
+ nonlocal_names, global_names = _collect_nonlocal_global_names(node.body)
298
+ excluded_scope_names = nonlocal_names | global_names
181
299
  for child in _walk_no_nested_scope(node.body):
182
- if isinstance(child, (ast.Nonlocal, ast.Global)):
183
- nonlocal_names.update(child.names)
184
300
  if isinstance(child, ast.Name) and isinstance(child.ctx, ast.Store):
185
301
  name = child.id
186
- if (name not in param_names and name != 'self' and name != 'cls'
302
+ if (name not in param_names and name not in excluded_scope_names
303
+ and name != 'self' and name != 'cls'
187
304
  and not name.startswith('__')):
188
305
  local_names.add(name)
189
- local_names -= nonlocal_names
190
306
 
191
307
  if not local_names:
192
308
  self.generic_visit(node)
@@ -484,6 +600,7 @@ class _OpaquePredicateInserter(ast.NodeTransformer):
484
600
  def obfuscate_source(source_code: str, seed=None) -> str:
485
601
  """对源码执行六重 AST 变换:去 docstring、局部变量重命名、
486
602
  字符串加密、常量折叠、控制流平坦化、虚假分支。
603
+ 函数、类和局部变量 annotation 默认保留。
487
604
  ast.unparse() 天然丢弃所有注释。
488
605
 
489
606
  :param seed: 随机种子;传入后可复现混淆结果
@@ -496,6 +613,7 @@ def obfuscate_source(source_code: str, seed=None) -> str:
496
613
  tree = _LocalVarRenamer().visit(tree)
497
614
  tree = _ControlFlowFlattener(rng=rng).visit(tree)
498
615
  tree = _OpaquePredicateInserter(rng=rng).visit(tree)
616
+ tree = _mark_annotation_constants(tree)
499
617
  tree = _StringEncryptor(rng=rng).visit(tree)
500
618
  tree = _ConstantFoldingObfuscator(rng=rng).visit(tree)
501
619
  ast.fix_missing_locations(tree)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AutoCython-zhang
3
- Version: 2.3.6
3
+ Version: 2.3.8
4
4
  Summary: 自动Cython,使用Cython批量编译.py文件为.pyd文件!
5
5
  Author-email: zhang_gavin <qq814608@163.com>
6
6
  License-Expression: MIT
@@ -20,7 +20,7 @@ Dynamic: license-file
20
20
 
21
21
  # AutoCython
22
22
 
23
- > 自动将 Python 源码编译为 Cython 二进制扩展(`.so`/`.pyd`),支持七重 AST 混淆、并发编译、跨平台运行。
23
+ > 自动将 Python 源码编译为 Cython 二进制扩展(`.so`/`.pyd`),支持六重 AST 混淆、并发编译、跨平台运行。
24
24
 
25
25
  [![PyPI](https://img.shields.io/pypi/v/AutoCython-zhang)](https://pypi.org/project/AutoCython-zhang/)
26
26
  [![Python](https://img.shields.io/pypi/pyversions/AutoCython-zhang)](https://pypi.org/project/AutoCython-zhang/)
@@ -33,7 +33,7 @@ AutoCython 是一个 Python 源码保护工具,通过 Cython 编译 + AST 混
33
33
  ## 特性
34
34
 
35
35
  - **一键编译** — 单文件 (`-f`) 或整目录 (`-p`) 批量编译为 `.so`/`.pyd`
36
- - **七重 AST 混淆** — docstring 移除、注解清除、局部变量重命名、字符串 XOR 加密、常量折叠、控制流平坦化、虚假分支注入
36
+ - **六重 AST 混淆** — docstring 移除、局部变量重命名、字符串 XOR 加密、常量折叠、控制流平坦化、虚假分支注入;默认完整保留运行时 annotation,兼容 FastAPI / Pydantic / dataclass
37
37
  - **并发编译** — 基于 `ThreadPoolExecutor` 的多线程并行编译,可配置并发数
38
38
  - **实时进度面板** — 基于 Rich 的实时任务状态表格、进度条、耗时统计
39
39
  - **跨平台** — 支持 Linux、macOS、Windows,自动适配 `.so`/`.pyd` 扩展名
@@ -50,6 +50,8 @@ pip install AutoCython-zhang
50
50
 
51
51
  > 当前编译链要求 `Cython>=3,<4`。该约束用于避免旧版 Cython 在编译后破坏 `Pydantic` / `FastAPI` / `dataclass` 等依赖运行时注解的框架行为。
52
52
 
53
+ > v2.3.8 修复了 `nonlocal` 闭包变量在混淆重命名后无法被 Cython 绑定的问题,适用于包含内层 flush/helper 函数的项目构建。
54
+
53
55
  ### 依赖
54
56
 
55
57
  | 包 | 用途 |
@@ -108,7 +110,7 @@ print(f"生成: {output}")
108
110
  |------|------|------|
109
111
  | `compile()` | `AutoCython.AutoCython` | 主入口:解析参数并调度编译任务 |
110
112
  | `compile_to_binary()` | `AutoCython.compile` | 将单个 `.py` 文件编译为二进制扩展 |
111
- | `obfuscate_source()` | `AutoCython.obfuscate` | 对源码执行七重 AST 混淆变换 |
113
+ | `obfuscate_source()` | `AutoCython.obfuscate` | 对源码执行六重 AST 混淆变换,并保留运行时 annotation |
112
114
  | `run_tasks()` | `AutoCython.run_tasks` | 并发任务执行引擎(含实时进度面板) |
113
115
  | `find_python_files()` | `AutoCython.tools` | 递归扫描目录下的可编译 `.py` 文件 |
114
116
  | `parse_arguments()` | `AutoCython.tools` | CLI 参数解析(中英双语) |
@@ -124,7 +126,7 @@ AutoCython/
124
126
  │ ├── _version.py # 单一版本号来源
125
127
  │ ├── AutoCython.py # 主编译调度逻辑
126
128
  │ ├── compile.py # Cython 编译核心(临时目录隔离)
127
- │ ├── obfuscate.py # 七重 AST 混淆引擎
129
+ │ ├── obfuscate.py # 六重 AST 混淆引擎
128
130
  │ ├── run_tasks.py # 并发任务执行 + Rich 实时面板
129
131
  │ └── tools.py # CLI 参数解析、文件扫描、国际化
130
132
  ├── tests/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AutoCython-zhang
3
- Version: 2.3.6
3
+ Version: 2.3.8
4
4
  Summary: 自动Cython,使用Cython批量编译.py文件为.pyd文件!
5
5
  Author-email: zhang_gavin <qq814608@163.com>
6
6
  License-Expression: MIT
@@ -20,7 +20,7 @@ Dynamic: license-file
20
20
 
21
21
  # AutoCython
22
22
 
23
- > 自动将 Python 源码编译为 Cython 二进制扩展(`.so`/`.pyd`),支持七重 AST 混淆、并发编译、跨平台运行。
23
+ > 自动将 Python 源码编译为 Cython 二进制扩展(`.so`/`.pyd`),支持六重 AST 混淆、并发编译、跨平台运行。
24
24
 
25
25
  [![PyPI](https://img.shields.io/pypi/v/AutoCython-zhang)](https://pypi.org/project/AutoCython-zhang/)
26
26
  [![Python](https://img.shields.io/pypi/pyversions/AutoCython-zhang)](https://pypi.org/project/AutoCython-zhang/)
@@ -33,7 +33,7 @@ AutoCython 是一个 Python 源码保护工具,通过 Cython 编译 + AST 混
33
33
  ## 特性
34
34
 
35
35
  - **一键编译** — 单文件 (`-f`) 或整目录 (`-p`) 批量编译为 `.so`/`.pyd`
36
- - **七重 AST 混淆** — docstring 移除、注解清除、局部变量重命名、字符串 XOR 加密、常量折叠、控制流平坦化、虚假分支注入
36
+ - **六重 AST 混淆** — docstring 移除、局部变量重命名、字符串 XOR 加密、常量折叠、控制流平坦化、虚假分支注入;默认完整保留运行时 annotation,兼容 FastAPI / Pydantic / dataclass
37
37
  - **并发编译** — 基于 `ThreadPoolExecutor` 的多线程并行编译,可配置并发数
38
38
  - **实时进度面板** — 基于 Rich 的实时任务状态表格、进度条、耗时统计
39
39
  - **跨平台** — 支持 Linux、macOS、Windows,自动适配 `.so`/`.pyd` 扩展名
@@ -50,6 +50,8 @@ pip install AutoCython-zhang
50
50
 
51
51
  > 当前编译链要求 `Cython>=3,<4`。该约束用于避免旧版 Cython 在编译后破坏 `Pydantic` / `FastAPI` / `dataclass` 等依赖运行时注解的框架行为。
52
52
 
53
+ > v2.3.8 修复了 `nonlocal` 闭包变量在混淆重命名后无法被 Cython 绑定的问题,适用于包含内层 flush/helper 函数的项目构建。
54
+
53
55
  ### 依赖
54
56
 
55
57
  | 包 | 用途 |
@@ -108,7 +110,7 @@ print(f"生成: {output}")
108
110
  |------|------|------|
109
111
  | `compile()` | `AutoCython.AutoCython` | 主入口:解析参数并调度编译任务 |
110
112
  | `compile_to_binary()` | `AutoCython.compile` | 将单个 `.py` 文件编译为二进制扩展 |
111
- | `obfuscate_source()` | `AutoCython.obfuscate` | 对源码执行七重 AST 混淆变换 |
113
+ | `obfuscate_source()` | `AutoCython.obfuscate` | 对源码执行六重 AST 混淆变换,并保留运行时 annotation |
112
114
  | `run_tasks()` | `AutoCython.run_tasks` | 并发任务执行引擎(含实时进度面板) |
113
115
  | `find_python_files()` | `AutoCython.tools` | 递归扫描目录下的可编译 `.py` 文件 |
114
116
  | `parse_arguments()` | `AutoCython.tools` | CLI 参数解析(中英双语) |
@@ -124,7 +126,7 @@ AutoCython/
124
126
  │ ├── _version.py # 单一版本号来源
125
127
  │ ├── AutoCython.py # 主编译调度逻辑
126
128
  │ ├── compile.py # Cython 编译核心(临时目录隔离)
127
- │ ├── obfuscate.py # 七重 AST 混淆引擎
129
+ │ ├── obfuscate.py # 六重 AST 混淆引擎
128
130
  │ ├── run_tasks.py # 并发任务执行 + Rich 实时面板
129
131
  │ └── tools.py # CLI 参数解析、文件扫描、国际化
130
132
  ├── tests/
@@ -1,6 +1,6 @@
1
1
  # AutoCython
2
2
 
3
- > 自动将 Python 源码编译为 Cython 二进制扩展(`.so`/`.pyd`),支持七重 AST 混淆、并发编译、跨平台运行。
3
+ > 自动将 Python 源码编译为 Cython 二进制扩展(`.so`/`.pyd`),支持六重 AST 混淆、并发编译、跨平台运行。
4
4
 
5
5
  [![PyPI](https://img.shields.io/pypi/v/AutoCython-zhang)](https://pypi.org/project/AutoCython-zhang/)
6
6
  [![Python](https://img.shields.io/pypi/pyversions/AutoCython-zhang)](https://pypi.org/project/AutoCython-zhang/)
@@ -13,7 +13,7 @@ AutoCython 是一个 Python 源码保护工具,通过 Cython 编译 + AST 混
13
13
  ## 特性
14
14
 
15
15
  - **一键编译** — 单文件 (`-f`) 或整目录 (`-p`) 批量编译为 `.so`/`.pyd`
16
- - **七重 AST 混淆** — docstring 移除、注解清除、局部变量重命名、字符串 XOR 加密、常量折叠、控制流平坦化、虚假分支注入
16
+ - **六重 AST 混淆** — docstring 移除、局部变量重命名、字符串 XOR 加密、常量折叠、控制流平坦化、虚假分支注入;默认完整保留运行时 annotation,兼容 FastAPI / Pydantic / dataclass
17
17
  - **并发编译** — 基于 `ThreadPoolExecutor` 的多线程并行编译,可配置并发数
18
18
  - **实时进度面板** — 基于 Rich 的实时任务状态表格、进度条、耗时统计
19
19
  - **跨平台** — 支持 Linux、macOS、Windows,自动适配 `.so`/`.pyd` 扩展名
@@ -30,6 +30,8 @@ pip install AutoCython-zhang
30
30
 
31
31
  > 当前编译链要求 `Cython>=3,<4`。该约束用于避免旧版 Cython 在编译后破坏 `Pydantic` / `FastAPI` / `dataclass` 等依赖运行时注解的框架行为。
32
32
 
33
+ > v2.3.8 修复了 `nonlocal` 闭包变量在混淆重命名后无法被 Cython 绑定的问题,适用于包含内层 flush/helper 函数的项目构建。
34
+
33
35
  ### 依赖
34
36
 
35
37
  | 包 | 用途 |
@@ -88,7 +90,7 @@ print(f"生成: {output}")
88
90
  |------|------|------|
89
91
  | `compile()` | `AutoCython.AutoCython` | 主入口:解析参数并调度编译任务 |
90
92
  | `compile_to_binary()` | `AutoCython.compile` | 将单个 `.py` 文件编译为二进制扩展 |
91
- | `obfuscate_source()` | `AutoCython.obfuscate` | 对源码执行七重 AST 混淆变换 |
93
+ | `obfuscate_source()` | `AutoCython.obfuscate` | 对源码执行六重 AST 混淆变换,并保留运行时 annotation |
92
94
  | `run_tasks()` | `AutoCython.run_tasks` | 并发任务执行引擎(含实时进度面板) |
93
95
  | `find_python_files()` | `AutoCython.tools` | 递归扫描目录下的可编译 `.py` 文件 |
94
96
  | `parse_arguments()` | `AutoCython.tools` | CLI 参数解析(中英双语) |
@@ -104,7 +106,7 @@ AutoCython/
104
106
  │ ├── _version.py # 单一版本号来源
105
107
  │ ├── AutoCython.py # 主编译调度逻辑
106
108
  │ ├── compile.py # Cython 编译核心(临时目录隔离)
107
- │ ├── obfuscate.py # 七重 AST 混淆引擎
109
+ │ ├── obfuscate.py # 六重 AST 混淆引擎
108
110
  │ ├── run_tasks.py # 并发任务执行 + Rich 实时面板
109
111
  │ └── tools.py # CLI 参数解析、文件扫描、国际化
110
112
  ├── tests/
@@ -42,7 +42,7 @@ markers = [
42
42
 
43
43
  [tool.setuptools.packages.find]
44
44
  where = ["."]
45
- exclude = ["tests*"]
45
+ exclude = ["tests*", "build*"]
46
46
 
47
47
  [tool.setuptools.dynamic]
48
48
  version = {attr = "AutoCython._version.__version__"}