.gen 文件定义
.gen 是 GenePad 的完整项目文件,文件本质是标准 SQLite 3 数据库
(文件头 16 字节为 SQLite format 3\0,MIME 登记为
application/vnd.genepad.database)。它比 .gjson 更完整,
除序列与注释外还保存引物结合位点、测序图谱(chromatogram)、比对条目、撤销/重做栈、
编辑历史和附件。任何语言的标准 SQLite 驱动都能直接读写——不需要安装任何专用解析库。
Overview
读取原则
- 无需自定义二进制 parser——用任意 SQLite 驱动打开
.gen,再按下方表结构查询即可。 sequence_blocks必须按block_index升序拼接,不能依赖数据库默认返回顺序。segments、qualifiers、components、alignment、references等字段是 JSON 字符串(chromatogram 的 trace 是「UTF-8 JSON 装进 BLOB」),需二次解析。.gen不持久化restrictionSites(酶切位点),由读取程序根据主序列重新检测:GenePad 在序列长度 ≤ 50,000 bp 时同步检测,超长序列走后台流式检测。- 保存出的
.gen永远是自包含单文件——GenePad 落盘前执行PRAGMA wal_checkpoint(TRUNCATE),用户目录里不会出现-wal/-shm伴生文件。 - 布尔值全部以字符串
'true'/'false'保存;键可能缺失,读取端要给默认值(见 project_meta 键参考)。
Getting Started
快速上手:读一个 .gen(Python)
Python 内置的 sqlite3 模块就够,零第三方依赖。下面 20 行代码读出元信息、完整序列和全部 feature:
import json
import sqlite3
con = sqlite3.connect("plasmid.gen")
con.row_factory = sqlite3.Row
# —— 元信息:key-value 表,布尔以字符串保存
meta = {row["key"]: row["value"] for row in con.execute(
"SELECT key, value FROM project_meta")}
is_circular = meta.get("isCircular") == "true"
# —— 主序列:必须按 block_index 升序拼接
sequence = "".join(row["bases"] for row in con.execute(
"SELECT bases FROM sequence_blocks ORDER BY block_index"))
print(meta.get("name", "Untitled"), len(sequence), "bp,",
"环状" if is_circular else "线状")
# —— features:segments / qualifiers 列是 JSON 字符串
for f in con.execute("SELECT * FROM features ORDER BY start_pos"):
length = f["end_pos"] - f["start_pos"] + 1
if f["start_pos"] > f["end_pos"]: # 跨原点的环形 feature
length = len(sequence) - f["start_pos"] + f["end_pos"] + 1
qualifiers = json.loads(f["qualifiers"]) if f["qualifiers"] else {}
print(f["name"], f["type"], f["strand"], length, "bp", qualifiers)
提示:坐标是 1-based inclusive,start=1, end=10 表示第 1~10 号碱基共 10 个;环形序列跨原点时 start > end(详见坐标与环形序列)。
可以下载真实示例文件试跑:41-pPUR-P2A-BFPnls-sgRNA1-RNF2_4AtoG_MS2-MA.gen(112 KB)——7,952 bp 的环形慢病毒 sgRNA 载体,含 25 个 feature、2 条引物及结合位点。它也是一个很好的教学样本:碱基全为小写(验证「不强制大写」)、isCircular/isDamMethylated 等布尔以字符串保存、feature Misc_Feature_1 带 4 段 segments。对它运行上面代码的输出:
41-pPUR-P2A-BFPnls-sgRNA1-RNF2_4AtoG_MS2-MA 7952 bp, 环状
hPGK promoter misc_feature forward 507 bp {}
Puromycin misc_feature forward 594 bp {}
P2A misc_feature forward 57 bp {}
TagBFP2 misc_feature forward 711 bp {}
...(共 25 个 feature)
Schema
核心表结构(全部 12 张)
project_meta项目元信息 key-value 表
| 字段 | 类型 | 说明 |
|---|---|---|
key | TEXT PRIMARY KEY | 元信息键 |
value | TEXT | 元信息值,布尔值也以字符串保存 |
保存时按 key upsert(INSERT ... ON CONFLICT(key) DO UPDATE),从不整表清空——因此旧文件里多余的自定义 key 会原样保留。下表是 GenePad 会写入的全部键:
| key | 写入值 | 读取行为 / 默认 | 说明 |
|---|---|---|---|
name | string | 缺失 → 'Untitled' | 项目名(GenBank LOCUS 名) |
description | string | 缺失 → '' | 描述(GenBank DEFINITION) |
isCircular | 'true'/'false' | 缺失 → false | 拓扑:环状 / 线状,无独立 topology 列 |
isDoubleStranded | 'true'/'false' | 缺失或任何非 'false' 值 → true | 双链 / 单链 |
isDamMethylated | 'true'/'false'/'' | 键缺失 → 未知(undefined);'' → false | Dam 甲基化状态 |
isDcmMethylated | 'true'/'false'/'' | 同上 | Dcm 甲基化状态 |
accession | string | 缺失 → undefined | GenBank ACCESSION |
organism | string | 缺失 → undefined | GenBank ORGANISM |
date | string | 缺失 → undefined | GenBank LOCUS 行日期 |
moleculeType | string | 缺失 → undefined | LOCUS 分子类型(DNA 等) |
division | string | 缺失 → undefined | GenBank division 代码 |
version | string | 缺失 → undefined | GenBank VERSION 行 |
keywords | string | 缺失 → undefined | GenBank KEYWORDS |
source | string | 缺失 → undefined | GenBank SOURCE |
comments | string | 缺失 → undefined | GenBank COMMENT |
references | JSON 字符串 GenBankRef[] | 非法 JSON → undefined | 文献条目,形状见下方 |
typeOfDisplay | 'file_name'/'custom' | 缺失 → 'file_name' | 标签页标题来源 |
customName | string | 缺失 → '' | 自定义显示名(typeOfDisplay 为 custom 时生效) |
fileName | string | 缺失 → undefined | 原始导入文件名;桌面端打开时会被实际文件名覆盖 |
isDirty | '0' | 加载时不读取 | 保存流程的簿记字段,每次落盘后写 '0' |
GenBankRef 的 JSON 形状(references 键的值):
// JSON: GenBankRef[]
{
"number": 1, // 必有
"authors": "Smith J., Lee K.",
"title": "Cloning of pBR322",
"journal": "Nucleic Acids Res 2026",
"pubmed": "12345678",
"remark": "..." // 其余字段均可省略
}
sequence_blocks主序列分块
| 字段 | 类型 | 说明 |
|---|---|---|
block_index | INTEGER PRIMARY KEY | 0-based block index |
bases | TEXT NOT NULL | 该块碱基字符串,除最后一块外恰为 10,000 bases |
SELECT bases FROM sequence_blocks ORDER BY block_index;
分块规则:每块 BLOCK_SIZE = 10000 bp,block_index = floor(offset / 10000)(offset 为 0-based 偏移)——块 0 存偏移 0–9999(即 1-based 位置 1–10000),块 1 存 10000–19999,最后一块可以不足 10,000。没有 start_pos 列:块起点 = block_index × 10000。序列总长用 SELECT COALESCE(SUM(LENGTH(bases)), 0) FROM sequence_blocks 计算。写入时全删全写(非增量更新),block_index 必须从 0 起连续无空洞——GenePad 按范围取块拼接,中间缺块会静默错位。碱基大小写保留原样(不强制大写),字符集为 IUPAC。
features序列注释
| 字段 | 类型 | 说明 |
|---|---|---|
id | TEXT PRIMARY KEY | feature id,GenePad 生成格式 {prefix}-{timestamp}-{counter} |
name | TEXT NOT NULL | feature 名称 |
type | TEXT NOT NULL | 如 gene, CDS, promoter, rep_origin, misc_feature |
start_pos | INTEGER NOT NULL | 1-based inclusive start |
end_pos | INTEGER NOT NULL | 1-based inclusive end |
strand | TEXT NOT NULL | forward / reverse / none |
color | TEXT | 显示颜色,CSS 颜色字符串(如 #e0502a),NULL 走默认色 |
label | TEXT | 显示标签(缺省用 name) |
note | TEXT | 备注 |
frame | INTEGER | 阅读框(0/1/2),可 NULL |
visible | INTEGER DEFAULT 1 | 1 可见,0 隐藏;缺省按 1 处理 |
segments | TEXT | JSON:FeatureSegment[],仅多片段(>1 段)feature 写入,否则 NULL |
qualifiers | TEXT | JSON:Record<string, string[]>,GenBank 限定词(/translation、/codon_start、/gene…) |
索引:idx_features_range ON features(start_pos, end_pos);GenePad 读取时按 ORDER BY start_pos 返回,范围查询用闭区间重叠判定 start_pos <= rangeEnd AND end_pos >= rangeStart。
两个 JSON 列的形状:
// segments 列 → JSON: FeatureSegment[](段数 > 1 时才写入)
[
{ "start": 1, "end": 120, "strand": "forward" },
{ "start": 500, "end": 874, "strand": "forward",
"name": " linker ", "color": "#3aaea0" } // name / color / strand 可选
]
// qualifiers 列 → JSON: Record<string, string[]>
{
"gene": ["AmpR"],
"codon_start": ["1"],
"translation": ["MSIQ..."]
}
primers / primer_binding_sites引物与结合位点(按 primer_id 关联)
| 字段 | 类型 | 说明 |
|---|---|---|
primers.id | TEXT PRIMARY KEY | 引物 id |
primers.name | TEXT NOT NULL | 引物名称 |
primers.sequence | TEXT NOT NULL | 引物序列(5'→3') |
primers.description | TEXT | 描述 |
primers.visible | INTEGER DEFAULT 1 | 1 显示,0 隐藏 |
primer_binding_sites.id | TEXT PRIMARY KEY | 固定格式 {primer_id}-site-{start} |
primer_binding_sites.primer_id | TEXT NOT NULL | 所属引物 |
primer_binding_sites.start_pos / end_pos | INTEGER NOT NULL | 1-based inclusive 结合范围 |
primer_binding_sites.bound_strand | TEXT NOT NULL | forward / reverse |
primer_binding_sites.annealed_bases | TEXT | 退火碱基,读取时 NULL → '' |
primer_binding_sites.melting_temperature | REAL | 解链温度,读取时 NULL → 0 |
primer_binding_sites.components | TEXT | JSON:PrimerComponent[],恒写入(可为 []) |
primer_binding_sites.alignment | TEXT | JSON:AlignmentSegment[],可 NULL |
索引:idx_binding_range ON primer_binding_sites(start_pos, end_pos)、idx_binding_primer ON primer_binding_sites(primer_id)。两个 JSON 列的形状:
// components 列 → JSON: PrimerComponent[]
[ { "hybridizedRange": "25-48", "bases": "ATGGC..." } ] // hybridizedRange 可选
// alignment 列 → JSON: AlignmentSegment[]
[
{ "type": "annealed", "primerBases": "ATG",
"templateBases": "TAC", "templateStart": 101, "templateEnd": 103 },
{ "type": "unannealed", "primerBases": "AAA",
"templateBases": "CGT", "templateStart": 104, "templateEnd": 106 },
{ "type": "del", "primerBases": "TT",
"templateBases": "", "templateStart": 107, "templateEnd": 107 }
]
base_color_ranges碱基颜色范围(手工着色)
| 字段 | 类型 | 说明 |
|---|---|---|
id | TEXT PRIMARY KEY | 颜色段 id |
start_pos | INTEGER NOT NULL | 1-based inclusive start |
end_pos | INTEGER NOT NULL | 1-based inclusive end |
strand | TEXT NOT NULL | 仅 forward / reverse(读取时其他值一律归为 forward) |
color | TEXT NOT NULL | 颜色值 |
索引:idx_base_color_ranges ON base_color_ranges(start_pos, end_pos, strand)。整表替换式写入(DELETE 后逐行 INSERT)。
alignment_entries比对条目(Sanger 测序比对到主序列)
| 字段 | 类型 | 说明 |
|---|---|---|
id | TEXT PRIMARY KEY | 比对条目 id |
name | TEXT NOT NULL | 条目名(通常为样品/引物名) |
sequence | TEXT NOT NULL | 被比对的读段序列 |
visible | INTEGER DEFAULT 1 | 1 显示,0 隐藏 |
features_json | TEXT | 预留列,当前版本不写入 |
chromatogram_json | TEXT | JSON:ChromatogramData(见下),可 NULL |
chromatogram_json 的形状(与 chromatogram_data 表同构,但这里存 JSON 字符串、非 BLOB):
// chromatogram_json → JSON: ChromatogramData
{
"traceA": [12, 45, 178, ...], // 四通道荧光强度,number[]
"traceC": [8, 31, 96, ...],
"traceG": [0, 12, 240, ...],
"traceT": [3, 60, 15, ...],
"peakLocations": [14, 25, 36, ...] // 峰位索引,number[]
}
chromatogram_data主序列测序图谱(id 固定 'primary')
| 字段 | 类型 | 说明 |
|---|---|---|
id | TEXT PRIMARY KEY | 主序列图谱固定为 'primary',整表仅此一行 |
trace_a / trace_c / trace_g / trace_t | BLOB | A/C/G/T 四通道荧光曲线 |
peak_locations | BLOB | 碱基峰位索引数组 |
BLOB 不是二进制浮点——内容是 JSON.stringify(number[]) 的 UTF-8 字节(TextEncoder 编码)。解码:JSON.parse(bytes.decode('utf-8')) / new TextDecoder().decode(blob)。写入流程为 DELETE WHERE id='primary' 再 INSERT,无该行表示主序列无图谱。
undo_entries撤销/重做栈(持久化,跨会话可用)
| 字段 | 类型 | 说明 |
|---|---|---|
sequence_number | INTEGER PRIMARY KEY AUTOINCREMENT | 单调递增序号,栈顶 = 最大值 |
stack_type | TEXT NOT NULL DEFAULT 'undo' | 'undo' 或 'redo'(同一张表存两条栈) |
operation_type | TEXT NOT NULL | 操作类型,取值见下 |
target_id | TEXT | 目标实体 id,序列级操作为 NULL |
before_diff / after_diff | TEXT | JSON:操作前/后差异负载,形状见下 |
栈机制:undo 栈上限 200 条,超出删最旧(按 sequence_number);每次压入新操作先清空整个 redo 栈;撤销 = 把最新 undo 行的 stack_type 翻转为 'redo';重做 = 读取后直接删除该行。operation_type 取值:addFeature, removeFeature, updateFeature, applyBaseColor, clearBaseColor, addPrimer, removePrimer, updatePrimer, insertBases, deleteBases, replaceBases, replaceSequence, addAlignmentEntry, removeAlignmentEntry。diff payload 形状:
// 实体操作:before/after 为完整对象或 null
add: { before: null, after: Feature | Primer }
update: { before: { id, changes }, after: { id, changes } } // changes = Partial<Feature|Primer>
applyBaseColor / clearBaseColor:
{ baseColorRanges: BaseColorRange[] }
// 序列编辑:附带编辑时刻的注释快照 + 编辑参数
InsertBasesDiff = Snapshot & { start: number, bases: string }
DeleteBasesDiff = Snapshot & { start: number, bases?: string, length?: number }
ReplaceBasesDiff = Snapshot & { start: number, oldBases?: string, newBases?: string }
// Snapshot = { features, baseColorRanges, primers, restrictionSites }
edit_history编辑审计日志(只增不删)
| 字段 | 类型 | 说明 |
|---|---|---|
id | INTEGER PRIMARY KEY AUTOINCREMENT | 行号 |
timestamp | TEXT NOT NULL | new Date().toISOString() 格式(如 2026-08-22T03:14:07.000Z) |
operation_type | TEXT NOT NULL | 与 undo_entries 同一套操作类型 |
target_id | TEXT | 目标实体 id,可 NULL |
description | TEXT | 人类可读描述,如 Added feature 'AmpR' at 12..874 |
before_snapshot / after_snapshot | TEXT | JSON:受影响实体(增删为完整对象)或小编辑对象(如 { position, bases }、{ start, oldBases }) |
只追加、无上限、保存时不清空——可以用它做外部审计或改动统计。
attachments / schema_version预留表
| 表 | 结构 | 状态 |
|---|---|---|
attachments | id TEXT PK, name TEXT NOT NULL, mime_type TEXT NOT NULL, data BLOB NOT NULL | 预留:schema 每次都会建表,但当前版本无写入入口(id 约定为 att-{timestamp},data 存原始字节) |
schema_version | key TEXT PK, value TEXT | 预留:当前版本从不写入行。常量 SCHEMA_VERSION = 5 仅存在于代码中 |
兼容机制因此不是版本号门控:GenePad 打开文件时执行幂等 DDL(全部 CREATE TABLE IF NOT EXISTS,外加两条吞掉「列已存在」错误的 ALTER TABLE features ADD COLUMN segments/qualifiers),旧文件缺列/缺表会在打开时自动补齐。外部读取程序应对缺列容错(老文件 features 没有 segments/qualifiers 列)。
连接参数:GenePad 打开数据库即执行 PRAGMA journal_mode=WAL 与 PRAGMA synchronous=NORMAL;保存前执行 PRAGMA wal_checkpoint(TRUNCATE) 再整体复制字节,所以成品 .gen 永远自包含。外部工具用默认 journal(delete 模式)写入同样合法——GenePad 重新打开时会自动切回 WAL。
Conventions
坐标与环形序列
feature、primer binding site、base_color_range 的 start_pos / end_pos 均为 1-based inclusive:第一个碱基的位置是 1,start=1, end=10 表示第 1 到第 10 个碱基,长度 = end - start + 1。
环形序列(isCircular='true')的 feature 跨越原点时直接存 start > end,长度公式变为 seqLength - start + end + 1——从 start 走到序列末尾,再从 1 绕回到 end。例:1000 bp 环形质粒上 start=995, end=5 覆盖 995–1000 加 1–5 共 11 个碱基。除 isCircular 标志外没有任何额外的环形专用字段。
多段 feature(如移码拼接、内含子跳过的 CDS)不用多行表示,而是把各段写进 segments JSON 数组,每段独立 { start, end, strand?, name?, color? },同样是 1-based inclusive、支持 start > end 跨原点。
注意两套体系:sequence_blocks.block_index 与序列偏移是 0-based,而所有注释坐标是 1-based。换算:1-based 位置 p 位于块 floor((p-1) / 10000)。
Behavior
保存与兼容行为
| 行为 | 规则 |
|---|---|
| 保存策略 | 内容表(sequence_blocks、features、base_color_ranges、primers、primer_binding_sites、chromatogram_data,及已加载比对的 alignment_entries)全删全写;project_meta 按 key upsert;undo_entries、edit_history、attachments 从不清空。没有「精简导出」选项——历史随文件走 |
| 批量写入 | sequence_blocks 每 100 行一条多值 INSERT,features 每 40 行一条(13 列 × 40 = 520 参数);无显式事务,逐条语句自动提交 |
| 序列实时写 | 编辑过程中序列整表重建(从位置 0 重写全部分块),不做块级增量更新 |
| 工作副本 | 打开 .gen 时先整体复制到临时目录(genepad-<random>.gen)再编辑,保存时 checkpoint 后把临时库字节整体写回目标路径 |
| 损坏容错 | 所有 JSON 列解析失败一律降级为 undefined/空,不报错;Android 导入路径会先校验 16 字节魔头 SQLite format 3\0 |
| 大小写 | 碱基大小写按原样存取(不强制大写);字符集为 IUPAC 扩展字母 |
| 加密 | 无——标准未加密 SQLite 文件,任何工具可读 |
Recipe
导出为 .gjson
把整个项目导出成 .gjson——GenePad 的文本 JSON 交换格式。下面这个脚本复刻了 GenePad 写出器的全部行为(键名、null 语义、空数组省略规则都一致),可直接交给 GenePad 或任何工具读回:
import json, sqlite3, time
def gen_to_gjson(path):
con = sqlite3.connect(path)
con.row_factory = sqlite3.Row
j = lambda s: json.loads(s) if s else None
meta = {r["key"]: r["value"] for r in con.execute("SELECT * FROM project_meta")}
n = lambda k: meta.get(k) or None # '' -> None:空串等同未设置
sequence = "".join(r["bases"] for r in con.execute(
"SELECT bases FROM sequence_blocks ORDER BY block_index"))
def segs_of(raw): # 多段(>1)feature 才带 segments 键
if not raw: return {}
segs = json.loads(raw)
return {"segments": [{"start": s["start"], "end": s["end"],
**({"strand": s["strand"]} if s.get("strand") else {})}
for s in segs]} if len(segs) > 1 else {}
# .gen 里这两个 JSON 列是 camelCase,.gjson 里是 snake_case
comp = lambda c: [{"hybridized_range": x.get("hybridizedRange"),
"bases": x["bases"]} for x in c]
align = lambda a: [{"type": x["type"], "primer_bases": x["primerBases"],
"template_bases": x["templateBases"],
"template_start": x["templateStart"],
"template_end": x["templateEnd"]} for x in a]
features = [{
"id": f["id"], "name": f["name"], "type": f["type"],
"start": f["start_pos"], "end": f["end_pos"], "strand": f["strand"],
"color": f["color"], "label": f["label"], "note": f["note"],
"frame": f["frame"], "visible": bool(f["visible"]),
**segs_of(f["segments"]), "qualifiers": j(f["qualifiers"]),
} for f in con.execute("SELECT * FROM features ORDER BY start_pos")]
primers = [{
"id": p["id"], "name": p["name"], "sequence": p["sequence"],
"description": p["description"],
"bindingSites": [{
"start": b["start_pos"], "end": b["end_pos"], "boundStrand": b["bound_strand"],
"annealedBases": b["annealed_bases"] or "",
"meltingTemperature": b["melting_temperature"] or 0.0,
"components": comp(json.loads(b["components"]) if b["components"] else []),
**({"alignment": align(json.loads(b["alignment"]))} if b["alignment"] else {}),
} for b in con.execute("SELECT * FROM primer_binding_sites WHERE primer_id = ?",
(p["id"],))],
"visible": None, # 写出器固定写 null
} for p in con.execute("SELECT * FROM primers")]
cnt = lambda t: con.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0]
doc = {
"version": "1.9",
"id": f"gjson-{int(time.time() * 1000)}", # 毫秒时间戳
"type_of_display": "file_name",
"custom_name": meta.get("name", "Untitled"),
"description": meta.get("description", ""),
"sequence": sequence,
"length": len(sequence),
"isCircular": meta.get("isCircular") == "true",
"isDoubleStranded": meta.get("isDoubleStranded") != "false",
"accession": n("accession"), "organism": n("organism"), "date": n("date"),
"moleculeType": n("moleculeType"), "division": n("division"),
"gbVersion": n("version"), "keywords": n("keywords"),
"source": n("source"), "comments": n("comments"),
"references": j(meta.get("references")),
"features": features,
**({"baseColorRanges": [{"id": r["id"], "color": r["color"],
"start": r["start_pos"], "end": r["end_pos"],
"strand": r["strand"]}
for r in con.execute("SELECT * FROM base_color_ranges")]}
if cnt("base_color_ranges") else {}),
**({"primers": primers} if primers else {}),
**({"alignmentEntries": [{"id": a["id"], "name": a["name"],
"sequence": a["sequence"], "visible": bool(a["visible"])}
for a in con.execute("SELECT * FROM alignment_entries")]}
if cnt("alignment_entries") else {}),
}
con.close()
return doc
# GenePad 写出 LF 换行;Windows 上 Python 需显式 newline="\n" 才能完全一致
with open("plasmid.gjson", "w", encoding="utf-8", newline="\n") as f:
json.dump(gen_to_gjson("plasmid.gen"), f, ensure_ascii=False, indent=2)
此脚本已与 GenePad 自身导出的同项目 .gjson 逐一核对:除两处原理性差异外完全一致——id(写出时刻的毫秒时间戳)与 feature 顺序(GenePad 按导出时的内存顺序,重开 .gen 后只能得到 start_pos 顺序)。可以用本页提供的两个样本文件自行复核:.gen 源文件、GenePad 导出的 .gjson。
转换要点:organism 等 GenBank 元数据在 .gen 里是空串 ''、在 .gjson 里写 null(空串等同未设置);结合位点 JSON 的键名从 camelCase 转为 snake_case;primers[].visible 恒为 null;baseColorRanges/primers/alignmentEntries 为空时整个键省略;测序图谱(chromatogram)不进 .gjson。完整规则见 .gjson 文件定义。
Recipe
写回:外部修改 .gen
GenePad 对外部改动完全宽容——只要仍是合法 SQLite 且符合上述约定,重新打开即生效:
import sqlite3
con = sqlite3.connect("plasmid.gen")
# 改 feature 颜色 / 项目名 / 拓扑
con.execute("UPDATE features SET color = ? WHERE name = ?", ("#7c5cff", "AmpR"))
con.execute("UPDATE project_meta SET value = 'true' WHERE key = 'isCircular'")
con.execute("UPDATE project_meta SET value = ? WHERE key = 'name'", ("pBR322-mod",))
# 增删碱基必须整表重建 sequence_blocks:block_index 从 0 连续,块长 10000
new_seq = "ATG" + "GCT" * 100 # ...你的新序列
con.execute("DELETE FROM sequence_blocks")
con.executemany("INSERT INTO sequence_blocks VALUES (?, ?)",
[(i // 10000, new_seq[i:i + 10000])
for i in range(0, len(new_seq), 10000)])
con.commit()
注意:改动碱基长度后要同步修正受影响 feature 的坐标(GenePad 不会自动重算);isDirty 只是保存簿记,加载时不读取,外部改动无需维护它。用 Python 默认 journal 写出的文件即为自包含单文件。
Recipe
从零创建 .gen
最小可用文件只需要三张表——GenePad 打开时会自动补齐其余表和索引。控制解读方式的 project_meta 键如下:
| key | 推荐写入 | 缺省时读取结果 |
|---|---|---|
isCircular | 'true'(环形)或 'false'(线形) | 线形 |
isDoubleStranded | 'true'(双链)或 'false'(单链) | 双链 |
isDamMethylated | 'true' / 'false',不写则不设置 | 未知(undefined) |
isDcmMethylated | 'true' / 'false',不写则不设置 | 未知(undefined) |
import json, sqlite3
name, seq = "pDemo", "ATGGCTAGC" * 111 # 999 bp 示例序列
con = sqlite3.connect("demo.gen")
con.executescript("""
CREATE TABLE project_meta (key TEXT PRIMARY KEY, value TEXT);
CREATE TABLE sequence_blocks(block_index INTEGER PRIMARY KEY, bases TEXT NOT NULL);
CREATE TABLE features (
id TEXT PRIMARY KEY, name TEXT NOT NULL, type TEXT NOT NULL,
start_pos INTEGER NOT NULL, end_pos INTEGER NOT NULL, strand TEXT NOT NULL,
color TEXT, label TEXT, note TEXT, frame INTEGER, visible INTEGER DEFAULT 1,
segments TEXT, qualifiers TEXT);
""")
con.executemany("INSERT INTO project_meta VALUES (?, ?)", {
"name": name,
"isCircular": "true", # 见上表;isDoubleStranded 缺省即双链
"isDirty": "0",
}.items())
BLOCK = 10000 # 与 GenePad 的 BLOCK_SIZE 一致
con.executemany("INSERT INTO sequence_blocks VALUES (?, ?)",
[(i // BLOCK, seq[i:i + BLOCK])
for i in range(0, len(seq), BLOCK)])
con.execute("INSERT INTO features VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", (
"feat-1", "GFP", "CDS", 101, 804, "forward",
None, None, None, None, 1, None,
json.dumps({"gene": ["gfp"]}), # qualifiers:Record<string, string[]>
))
con.commit()
要点:feature 的 13 列一个都不能少(后几列写 NULL 即可);块长 10000 且 block_index 连续;布尔键用小写字符串。之后 GenePad(或任何读取程序)就能直接打开这个文件。
Quick Look
用 sqlite3 命令行看一眼
# 打开 .gen(本质就是 SQLite;可换成本页提供的示例文件)
sqlite3 example.gen
# 列出所有表 / 建表语句
.tables
.schema features
# 读取项目名 / 拓扑
SELECT key, value FROM project_meta WHERE key IN ('name','isCircular');
# 拼接完整序列(注意按 block_index 排序)
SELECT group_concat(bases, '') FROM (SELECT bases FROM sequence_blocks ORDER BY block_index);
# 查看前几个 feature(坐标 1-based inclusive)
SELECT name, type, start_pos, end_pos, strand FROM features ORDER BY start_pos LIMIT 5;
# 看绑定位点 / 编辑历史
SELECT primer_id, start_pos, end_pos, bound_strand FROM primer_binding_sites LIMIT 5;
SELECT timestamp, description FROM edit_history ORDER BY id DESC LIMIT 5;
# 解码主序列测序图谱的 A 通道(BLOB 里是 UTF-8 JSON 数组)
SELECT CAST(trace_a AS TEXT) FROM chromatogram_data WHERE id = 'primary';
# 顺手验证魔头:应为 "SQLite format 3" + \0
xxd -l 16 example.gen
只读场景请用 sqlite3 "file:example.gen?immutable=1" 打开——避免在文件旁产生 -wal/-shm 副产品(GenePad 自带的质粒库扫描器就是这么做的,Rust 示例见Rust 读取)。
See Also
延伸
- .gjson 文件定义——单文件 JSON 交换格式,适合脚本生成与轻量共享。
- .dna 转换——SnapGene
.dna的读取与转换说明。 - Rust 读取示例——rusqlite 依赖配置、只读(immutable)打开与 .gjson 读取。