Python 工程化: 用 Copier 打造“自我进化“的项目脚手架

张开发
2026/4/11 9:58:28 15 分钟阅读

分享文章

Python 工程化: 用 Copier 打造“自我进化“的项目脚手架
什么是 copier安装QuickStart底层机制创建模板模板辅助函数全局变量配置模板exclude: 排除文件tasks: 项目生成/更新后要执行的命令复制项目更新项目什么是 copierCopier 是一个开源的项目模板生成工具基于 Jinja2 模板引擎模板支持本地路径 和 Git URL项目可以包含任意文件支持动态替换任何文本文件中的值双向同步: 如果模板发生变化, 可以将更新同步到已生成的项目中安装# 基本安装uv toolinstallcopier# 安装插件uv toolinstallcopier--withcopier-template-extensionsQuickStart创建一个模板: my_copier_template # your template project ├── copier.yml # your template configuration ├── {{project_name}} # a folder with a templated name │ └── {{module_name}}.py.jinja # a file with a templated name └─ {{_copier_conf.answers_file}}.jinja # answers are recorded herecopier.yml文件内容:project_name:type:strdefault:{{ _folder_name }}help:What is your project name?module_name:type:strhelp:What is your Python module name?{{module_name}}.py.jinja文件内容:print(Hello from {{module_name}}!){{_copier_conf.answers_file}}.jinja文件内容:# Changes here will be overwritten by Copier {{ _copier_answers|to_nice_yaml -}}从模板生成项目:copier copy path/to/template path/to/destionation底层机制Copier 运转依赖两个基石:Jinja2模板渲染 Git差异对比与合并. 最核心的秘密武器是它在目标项目下生成的.copier-answers.yml文件。这个文件相当于项目的 DNA 记录它记住了你用的是哪个版本的模板以及你当时回答了什么问题项目名、作者等操作逻辑:定义: 创建一个包含copier.yml定义要问用户的问题和模板文件包含{{ question_answer }}变量的 模板生成: 运行copier copy 模板URL 本地目录。 Copier 提问 - 渲染文件 - 存下.copier-answers.yml更新: 当你修改了模板, 在本地项目目录运行copier update先重新生成一个 “旧版本模板对应的干净项目”用它和 “当前项目” 做 diff, 算出你后来做过哪些改动再把当前项目按新模板升级最后再把刚才那份 diff 补回来 (有冲突则抛出)创建模板模板是一个目录, 生成项目时:普通文件: 原样复制到目标位置.jinja结尾: 模板引擎将渲染它们如果一个带后缀的文件与另一个不带后缀的文件相邻, 不带后缀的文件将被忽略模板辅助函数除了 Jinja 支持的所有功能外, Copier 还支持 jinja2-ansible-filters 提供的所有函数和过滤器{{ project_name | b64encode }} {{ project_name | md5 }} {{ project_name | replace(a, A) }}全局变量如下变量在 Jinja 模板中始终可用:_copier_answers: 问题答案字典_folder_name: copy命令后跟的项目根目录的名称…配置模板如下为 copier.yml 中支持的配置exclude: 排除文件_exclude:-copier.yml-extensions.py-.git-.git/**-__pycache__-__pycache__/**-.ruff_cache-.venv-uv.locktasks: 项目生成/更新后要执行的命令_tasks:-command:git initwhen:{{ _copier_operation copy }}-command:git add .when:{{ _copier_operation copy }}-command:git commit-m Initial commitwhen:{{ _copier_operation copy }}-command:uv venv .venv--python{{python_version}}when:{{ _copier_operation copy }}复制项目# 建议使用完整路径, 避免后续 copier update 找不到模板路径copier copy path/to/project/template path/to/destination# --data 参数, 通过命令行指定问题的答案默认情况下, copier 会复制最新Git tag对应的版本, 按照 PEP440 进行排序0.9 0.9.1 0.9.2 ... 0.9.10 0.9.11 1.0 1.0.1 1.1 2.0 2.0.1 ...可以使用 --vcs-ref 参数来指定要使用的分支、tag 或 引用# 使用仓库中 main 分支copier copy --vcs-ref main https://github.com/foo/copier-template.git ./path/to/destination# 使用本地模板中最新版本包含未提交的修改copier copy --vcs-ref HEAD path/to/project/template path/to/destination更新项目当如下条件满足时, 才可以更新项目:目标文件夹中包含一个有效的.copier-answers.yml文件模板使用 git 进行版本控制 (with tags)目标文件夹使用 git 进行版本控制进入目标文件夹, 确保 git status 是干净的, 然后运行:copirt update# 从指定分支、tag 或 引用 更新项目copirt update --vcs-ref xx# 重用之前的所有回答copier update--defaults# 只更改某个问题copier update--defaults--dataupdated_questionmy new answer若更新遇到冲突, 可通过如下参数控制冲突处理方式:--conflict rej: 为每个有冲突的文件创建一个单独的 .rej 文件--conflict inline: (default) 在冲突的文件中创建冲突标记, 与 git merge 标记类似为避免发生损坏, 建议在更新前提交一次版本:gitstatusgitadd-Agitcommit-mbefore copier updatecopier update

更多文章