Skip to main content

Python Module Bank - 单文件模块仓库

把 Python 模块打包进一个文件(.mbank),运行时直接从包里导入,支持 AES 字节码加密 和 PyArmor 代码混淆 双重保护。

存储层是 sqlite_vfs: .mbank 就是一个 标准 svfs 包——一个用 SQLite 实现的虚拟文件系统,模块、源码、 PyArmor 运行时都以文件形式躺在里面。

🌟 特性

  • 单文件分发 - 所有模块打包到单个 .mbank;它同时是标准 .svfs 包,svfs / vfsverify / vfsunpack / svfs publish 这些工具都能直接处理它
  • AES 字节码加密 - 编译后的字节码使用 Fernet (AES-128-CBC) 加密存储
  • PyArmor 代码混淆 - 支持使用 PyArmor 对 Python 源代码进行混淆保护
  • 双层保护 - 可同时使用 PyArmor 混淆 + AES 字节码加密,提供双重安全保障
  • 动态导入 - 运行时直接从包加载模块,无缝集成 Python 导入系统
  • 完整包支持 - 支持包结构和子模块导入
  • 内容去重与压缩 - 内容按 sha256 寻址(同一份运行时/文件只存一次),写入时 zlib 压缩(压不动的内容原样存,不浪费)
  • 可校验 - 快照带摘要,vfsverify 能验证包内容是否被改过
  • CLI 工具 - 提供完整的命令行接口

📦 安装

从源码安装

git clone http://124.71.68.6:3000/chakcy/module_bank.git
cd module-bank
pip install -e .

使用 uv(推荐)

git clone http://124.71.68.6:3000/chakcy/module_bank.git
cd module-bank
uv sync

可选依赖

  • PyArmor (强烈推荐): uv add pyarmor 或 pip install pyarmor
    • 为模块提供 Python 源代码级别的混淆保护
    • 商业版可去除试用版水印

存储层依赖

sqlite-vfs 是必装依赖,在 pyproject.toml 里以 git 依赖声明。发布到 PyPI 之前, v0.4.0 那个 tag 必须先推到 Gitea,否则 uv sync / pip install 装不上(那个 仓库是私有的话,使用者还要能访问它,比如配好凭据助手或 .netrc)。

本地开发期间还没推 tag 时,把 pyproject.toml 里注释掉的 [tool.uv.sources] 覆盖打开(指向本机 sqlite_vfs 目录),或者直接 uv pip install -e <sqlite_vfs 路径>。

🚀 快速开始

1. 创建示例模块

# my_package/__init__.py
def hello():
    print("Hello from my_package!")
    return "success"

VERSION = "1.0.0"
# my_package/utils.py
def add(a, b):
    return a + b

2. 打包模块到数据库

使用 AES 加密(推荐)

# 生成密钥并打包
mbank pack my_package --db my_package.mbank --key "$(python -c "from module_bank.encryption import Encryption; print(Encryption.generate_key())")"

使用 PyArmor 混淆

# 使用 PyArmor 保护源代码
mbank pack my_package --db my_package.mbank --pyarmor

使用双层保护

# PyArmor 混淆 + AES 加密
mbank pack my_package --db my_package.mbank --pyarmor --key "your-encryption-key"

3. 从数据库导入

# import_example.py
from module_bank.python_to_sqlite import PythonToSQLite

packer = PythonToSQLite("my_package.mbank")
finder = packer.install_importer(key="your-encryption-key")  # 如果加密了需要提供密钥

from my_package import utils
from my_package import hello

print(hello())
print(utils.add(3, 4))

🛡️ 安全保护机制

第一层:PyArmor 代码混淆

PyArmor 对 Python 源代码进行混淆,将代码转换为难以阅读和逆向的形式:

  • 混淆后的代码依赖运行时二进制文件(.pyd/.so),无法直接运行
  • 支持混淆级别控制(代码混淆、模块混淆)
  • 运行时文件会自动存储在包中(runtime/),加载时自动部署到临时目录
  • 整个包只跑一次 PyArmor,因此整包只存一份运行时:PyArmor 每次调用都会 生成一份自己的运行时(同名、内含那一次的新密钥、约 639 KB),逐模块调一次就会 让包体积按模块数线性膨胀(实测 6 个模块 2.3 倍、30 个模块约 6 倍)
# 高级混淆选项
mbank pack my_module.py --name my_module --db modules.mbank \
    --pyarmor \
    --pyarmor-obf-code 1 \
    --pyarmor-obf-mod 1

第二层:AES 字节码加密

编译后的字节码使用 Fernet (AES-128-CBC) 加密:

# 生成密钥
python -c "from module_bank.encryption import Encryption; print(Encryption.generate_key())" > my_key.key

# 使用密钥打包
mbank pack my_module.py --name my_module --db modules.mbank --key "$(cat my_key.key)"

# 使用时提供密钥
mbank install --db modules.mbank --key "$(cat my_key.key)"

把密钥存进包(可选,注意权衡)

packer_key / get_key 可以把打包密钥写进包里,省掉"分发时要另传密钥"的麻烦:

packer = PythonToSQLite("modules.mbank", key=aes_key)
packer.pack_directory("src/")
packer.packer_key(aes_key)     # 存进包根部(是 packer_key 文件,不是模块)
packer.close()

# 使用方拿到包就能直接跑(不用再传 key)
packer = PythonToSQLite("modules.mbank", readonly=True)
key = packer.get_key()          # 没存过返回 None
packer.install_importer(key=key)

先把话说清楚:密钥和密文躺在同一个文件里,AES 这一层对"包泄露"就不再是 保护——能读包的人就能解开。它的价值是省事,真正的保护来自 PyArmor 那一层。 要真保密,就别用这两个方法,让使用方自己持有 key。

安全建议

  1. 密钥管理:加密密钥不要硬编码在代码中,使用环境变量或密钥管理服务
  2. 删除源代码:打包后可从数据库删除源代码,仅保留混淆后的代码:
# 删除所有模块的源代码
mbank delete_source --db modules.mbank

# 删除指定模块的源代码
mbank delete_source --db modules.mbank --module my_module

📖 详细使用

命令行工具

# 打包单个模块
mbank pack my_module.py --name my_module --db modules.mbank

# 打包整个目录(自动识别包结构)
mbank pack my_package/ --db modules.mbank

# 使用 PyArmor 混淆打包
mbank pack my_module.py --name my_module --db modules.mbank --pyarmor

# 使用 AES 加密打包
mbank pack my_module.py --name my_module --db modules.mbank --key "your-key"

# 双层保护
mbank pack my_module.py --name my_module --db modules.mbank --pyarmor --key "your-key"

# 列出数据库中的模块
mbank list --db modules.mbank

# 删除源代码
mbank delete_source --db modules.mbank
mbank delete_source --db modules.mbank --module my_module

# 安装导入器并进入交互模式
mbank install --db modules.mbank
mbank install --db modules.mbank --key "your-key"

编程接口

打包模块

from module_bank import PythonToSQLite
from module_bank.encryption import Encryption

# 生成加密密钥
key = Encryption.generate_key()

# 使用 AES 加密打包
packer = PythonToSQLite("modules.mbank", key=key)

# 或使用 PyArmor 混淆打包
packer = PythonToSQLite(
    "modules.mbank",
    pyarmor_obfuscate=True,
    pyarmor_obf_code=1,
    pyarmor_obf_mod=1,
)

# 打包单个模块
packer.pack_module("module.py", "module_name")

# 打包整个目录(自动识别包结构)
packer.pack_directory("my_package/")

# 验证包结构
packer.verify_package_structure()

导入模块

from module_bank import PythonToSQLite

# 如果数据库中的模块被 AES 加密了,需要提供密钥
packer = PythonToSQLite("modules.mbank")

# 安装导入器到 sys.meta_path
finder = packer.install_importer(key="your-key")

# 列出所有可用模块
modules = packer.list_modules()
for module in modules:
    package_flag = " [包]" if module["is_package"] else ""
    print(f"  - {module['module_name']}{package_flag}")

# 直接导入数据库中的模块(就像普通模块一样)
import my_package
import my_package.submodule

print(my_package)
my_package.hello()

🏗️ 架构设计

核心组件

src/module_bank/
├── layout.py                   # 包内布局:模块名 ↔ svfs 路径的唯一映射处
├── python_to_sqlite.py         # 主打包类
├── sqlite_module_importer.py   # 存储管理器(写侧:模块与 PyArmor 运行时)
├── sqlite_meta_path_finder.py  # 元路径查找器(只读打开 + 模块清单缓存)
├── sqlite_module_loader.py     # 模块加载器(读包、解密、部署运行时)
├── encryption.py               # AES 密钥管理
├── pyarmor_encryption.py       # PyArmor 混淆封装
├── cli.py                      # 命令行接口
└── __init__.py                 # 模块导出

数据流

1. 打包阶段:
   .py 文件 → (可选: PyArmor 混淆) → 编译为字节码 → (可选: AES 加密)
            → 作为文件写进 svfs 包(modules/ 下)

2. 导入阶段:
   导入请求 → MetaPathFinder 查内存缓存 → ModuleLoader 读包
            → (AES 解密) → (PyArmor 运行时部署到临时目录) → 执行模块

包内布局

.mbank 是一个标准 svfs 包(schema v3),把它 unpack 出来就是下面这棵树—— 所以"包里有什么"看一眼目录就知道:

bank.mbank
├─ modules/a/b.pyc                      字节码(marshal,可选 AES 加密)← 模块存在的锚点
├─ modules/a/b.py                       源码(PyArmor 混淆后的原文;可删)
├─ modules/a/b.meta.json                标志位与用户元数据(全默认值时不写这个文件)
├─ modules/pkg/__init__.pyc             包(is_package)用 __init__
├─ modules/pkg/__init__.py
├─ modules/pkg/__init__.meta.json
├─ runtime/pyarmor_runtime_000000/pyarmor_runtime.pyd
└─ runtime/pyarmor_runtime_000000/__init__.py

几条约定:

  • 点分模块名里的点换成目录分隔符:a.b → modules/a/b,所以包的字节码落在 modules/a/b/__init__.pyc,与普通模块 modules/a/b.pyc 不冲突; is_package 完全由路径决定(不记在元数据里,免得两处说法打架)

  • .pyc 是锚点:每个模块一定有字节码(打包时总是从源码编译),源码和元数据 都是可选的——delete_source 删掉的就是 .py 那个文件

  • 元数据文件里只有不可推导的信息:

    {
      "format": 1,
      "encrypted": false,
      "pyarmor_protected": true,
      "pyarmor_runtime": "pyarmor_runtime_000000",
      "metadata": { "version": "1.0" }
    }
    
  • 一个模块的源码 + 元数据 + 字节码写在同一个事务里,所以不会出现"新元数据配 旧字节码"的中间态

用 svfs 的工具链直接操作包

因为 .mbank 就是 svfs 包,下列操作不需要 module_bank 参与:

svfs info bank.mbank              # 统计与快照信息
vfsverify bank.mbank              # 校验内容摘要
vfsunpack bank.mbank ./out        # 导出成上面那棵目录树
svfs sign bank.mbank --key "$K"   # 给快照做 HMAC 签名(防篡改)
from sqlite_vfs.core import SQLiteVFS

vfs = SQLiteVFS("bank.mbank", readonly=True)
print(vfs.get_stats())
print([i["name"] for i in vfs.list_directory("modules")])
vfs.close()

🔧 高级功能

排除模式

# 打包时排除特定文件
packer.pack_directory(
    "my_project/",
    exclude_patterns=["*_test.py", "*.pyc", "__pycache__"],
)

元数据存储

# 为模块添加元数据
packer.importer.add_module(
    "my_module",
    source_code,
    is_package=False,
    metadata={"version": "1.0", "author": "me"},
)

混合导入

# 数据库导入器默认插入到 meta_path 开头,优先级高于文件系统
import sys
from module_bank import PythonToSQLite

packer = PythonToSQLite("modules.mbank")
finder = packer.install_importer()

# 如果需要文件系统优先,将查找器追加到末尾
sys.meta_path.remove(finder)
sys.meta_path.append(finder)

验证运行时完整性

from module_bank.sqlite_module_importer import SQLiteModuleImporter

importer = SQLiteModuleImporter("modules.mbank")
runtimes = importer.list_runtimes()
for rt in runtimes:
    print(f"运行时: {rt['runtime_name']} (创建于: {rt['created_at']})")

⚠️ 注意事项

安全性

  • 模块字节码直接执行,确保包来源可信
  • AES 加密密钥必须安全保管,丢失后将无法解密模块
  • PyArmor 混淆提供源代码级别的保护,但商业版才能去除试用版水印
  • 生产环境建议 PyArmor 混淆 + AES 加密同时使用
  • delete_source 会把源码文件及其内容从包里删掉(svfs 会回收不再被引用的内容), 但和所有 SQLite 删除一样,这不是安全擦除:旧内容可能还留在数据库的空闲页里。 真要彻底消除,打一个不含源码的新包,或者打完之后用 VACUUM 压一遍

兼容性

  • 0.4.0 起存储层换成 sqlite_vfs:.mbank 从"自建的表结构"变成 svfs 包 (modules/ + runtime/ 文件树)。旧包不兼容,需要用 mbank pack 重新打包—— 旧包被打开时会直接报错说明这一点,不会静默给出一个空包
  • 字节码不跨 Python 版本兼容(3.12 编译的模块无法在 3.11 加载)
  • 不支持 C 扩展模块
  • PyArmor 运行时文件与操作系统相关(Windows 用 .pyd,Linux 用 .so)
  • 运行时文件需在目标系统上重新打包
  • Python 3.10+(跟着 sqlite-vfs 与本项目实际用到的语法走)

性能

  • 启动时:一次遍历包内容建模块清单(纯内存查表,与包大小无关),外加字节码 反序列化/运行时部署开销
  • 运行时:与传统导入性能相同(使用 sys.modules 缓存)
  • 最佳适用:长期运行的服务、桌面应用

更新模块

# 重新打包会自动更新(同一批文件在一个事务里替换,不会出现新旧混搭)
packer.pack_module("updated_module.py", "module_name")

删除模块

没有单独的"删模块"命令:不再需要的模块用 svfs 直接删掉目录项即可。更常见的 做法是删掉源码只留字节码(见上面的 delete_source)——那是一份 .mbank 该 有的状态。

from sqlite_vfs.core import SQLiteVFS

vfs = SQLiteVFS("modules.mbank")
vfs.remove("modules/module_to_remove.pyc")   # 锚点没了,模块即不存在
vfs.remove("modules/module_to_remove.py")
vfs.close()

备份与恢复

# 包是单个文件,易于备份
cp modules.mbank modules.backup.mbank

# 恢复
cp modules.backup.mbank modules.mbank

版本管理

包里的版本就是 svfs 的快照(版本之间共享内容 blob,多留一版几乎不占空间)。 分工是:sqlite_vfs 只负责"把你要的那一版内容给你",选哪一版、什么时候留版 是 module_bank 这一层的事。

定格一个版本

from module_bank import PythonToSQLite

packer = PythonToSQLite("modules.mbank")
packer.pack_directory("src/")
packer.tag_version("release-1.0")   # 把当前内容定格成 release-1.0
packer.close()

# 之后继续改、继续打包,写的是自动派生的工作版本(名字叫 work),
# release-1.0 那份不再变动
packer = PythonToSQLite("modules.mbank")
packer.pack_directory("src/")
packer.tag_version("release-2.0")
for v in packer.list_versions():
    print(v["id"], v["name"], v["total_files"], "当前" if v["current"] else "")
packer.close()

为什么 tag_version 不是简单调一次 create_snapshot(name):svfs 的快照 语义是"当前那一版"(写入落进当前版本),所以 create_snapshot("release-1.0", copy_entries=True) 得到的名字落在会继续变动的那一版上——实测结果是打完 v1、tag 成 release-1.0、再打 v2 之后,release-1.0 里是 v2。tag_version 因此是"先把当前快照改名(定格),再派生一个工作版本接手后续写入"。

从指定版本加载

# 加载 release-1.0 那一份(id 或名字都行)
packer = PythonToSQLite("modules.mbank", readonly=True, snapshot="release-1.0")
packer.install_importer()      # 之后 import 到的都是 v1.0 的代码
packer.close()

不指定 snapshot 就是跟着最新(工作版本)走;指定了就钉死在那一版, 包后续新增多少版本都不影响它——灰度、回滚、给老客户重发旧版都用这个。

snapshot 也可以传给 install_importer(snapshot=...),或者在包里用 use_version(...) 切换当前版本。可写句柄不能直接打开一个历史版本(那是 "顺手改掉发出去的那份"):确实要改,先显式 use_version("release-1.0")。

mbank list --db modules.mbank --snapshot release-1.0   # 看某一版里有什么
mbank install --db modules.mbank --snapshot release-1.0

防篡改:签名与验签

签的是快照摘要(HMAC-SHA256,对称密钥,只挡"发出后被改过",不挡伪造):

packer = PythonToSQLite("modules.mbank", readonly=True)
packer.sign_version("共享密钥", key_id="release-1.0")   # 或先切到那一版再签
report = packer.verify_version("共享密钥")              # {"ok": bool, "errors": [...]}
packer.close()

验不验签是加载方的策略,默认不验(不传就当没这回事):

# 加载前先验,验不过拒绝加载(抛 SignatureVerificationError)
finder = packer.install_importer(
    signature_key="共享密钥",
    signature_key_id="release-1.0",   # 可选:只认这个 key_id 的签名
    allow_unsigned=False,             # 那一版还没签过时是否放行(默认放行)
)
mbank install --db modules.mbank --signature-key "$KEY" --require-signature

verify_version / install_importer 的验签都在只读连接上完成,不需要写权限。 更细的操作(快照清单、导出、发 CDN)直接用 svfs 的工具链:

svfs info modules.mbank              # 统计与当前快照
vfsverify modules.mbank              # 校验内容摘要
vfsunpack modules.mbank ./out        # 导出成目录树
svfs sign modules.mbank --key "$K"   # 命令行签名

📚 应用场景

  1. 商业软件分发 - PyArmor 混淆保护源代码知识产权
  2. 安全敏感环境 - AES 加密确保即使数据库泄露也无法还原代码
  3. 插件系统 - 动态加载数据库中的加密/混淆插件模块
  4. 教育平台 - 安全分发练习代码,防止学生抄袭
  5. 微服务 - 打包多个服务模块到单个加密文件
  6. 嵌入式系统 - 减少文件系统依赖,提高安全性

注意: 本工具主要用于模块分发和部署场景,不适合开发阶段的频繁修改。

许可证: MIT License

Metadata

Release files for module-bank 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for module-bank 0.4.1
File Interpreter ABI Platform
module_bank-0.4.1-py3-none-any.whl Python 3 none any Details

Release files / module_bank-0.4.1-py3-none-any.whl

Download URL module_bank-0.4.1-py3-none-any.whl
Size 44.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b18b6078d79fd5d6b7f9cace08aad5f96917159718bba4f53d4ce277c2061b1b
BLAKE2b-256 checksum
How to use checksums
16dee9a714cdf40aa8b95599edc11e064194741986b5d7a30689524531c7c15c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release history Release notifications | RSS feed

0.4.2

1 release file

This release

0.4.1 This release

1 release file

0.4.0

1 release file

0.3.2

1 release file

0.3.1

1 release file

0.3.0

1 release file

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page