无需收集全部数据的 JSON Lines 导出
目标
把惰性生成、行边界、公开字段和输出上限,与 HTTP 流连接起来。
为什么重要
管理员一下载所有订单,服务器内存就猛增。原因是 export 函数在生成 JSON 之前,先把所有行都收集进了列表。后来改成了按行输出,可是正文里的换行破坏了真正的行边界,内部成本也原样发了出去。流式传输不是改一下返回类型就行,而是要一并确定惰性求值和表示契约。
步骤
- 在
/root/work/fa-jsonl-export-lab/service.py中,validate_row(row) 在它是 dict,id 是排除 bool 的正 int,name 是非空 str 时,返回 row。其余都是 ValueError。允许有额外的内部字段。
先做一次准备。已有的文件不会被覆盖。
mkdir -p /root/work/fa-jsonl-export-lab
test -e /root/work/fa-jsonl-export-lab/service.py || cp /opt/fixtures/ten_labs/fa-jsonl-export-lab/service.py /root/work/fa-jsonl-export-lab/service.py
cd /root/work/fa-jsonl-export-lab
-
在
/root/work/fa-jsonl-export-lab/service.py中,project(row) 在 validate_row 之后,返回只含 id 和 name 的新 dict。原件的内部字段保持原样。 -
在
/root/work/fa-jsonl-export-lab/service.py中,encode_line(row) 是一个 str:把 project 的结果用 ensure_ascii=False、separators=(',',':')、sort_keys=True 编码为 JSON,并在末尾附加一个 ' '。name 中的换行必须是 JSON 转义。 -
在
/root/work/fa-jsonl-export-lab/service.py中,validate_max(value) 只原样返回排除 bool 的 1–1000 的 int,其余都是 ValueError。 -
在
/root/work/fa-jsonl-export-lab/service.py中,take_rows(rows, maximum) 是一个 iterator,它借助 islice 等方式,最多只惰性地返回 maximum 条。调用时验证 maximum,每执行一次 next,只消耗一次输入。 -
在
/root/work/fa-jsonl-export-lab/service.py中,json_lines(rows, maximum=100) 对从 take_rows 得到的每一行,yield encode_line。不要返回全部拼接好的字符串或列表。 -
在
/root/work/fa-jsonl-export-lab/service.py中,decode_lines(text) 对 splitlines 中每一个非空的行,先 json.loads,再 validate_row,然后作为列表返回。空字符串是 [],空的中间行是 ValueError。 -
在
/root/work/fa-jsonl-export-lab/service.py中,create_app(rows) 在 GET /export 中,把 json_lines(rows, 100) 作为 application/x-ndjson 的 StreamingResponse 返回。rows 是可以再次遍历的列表。必须没有内部字段,并保留每一行的内容和顺序。
参考
- 不联网、不安装软件包,在现有的 lab-dev 环境中进行。
- 每一步都在 45 秒的评分预算内运行。不要添加真实的 sleep 或网络调用。
- 评分会重新导入提交的模块,并用独立的输入和临时 DB 进行检查。请实现契约,而不要把预期值当作常量返回。
- FastAPI 官方文档 · pytest 官方文档 · Python sqlite3
- 局限:TestClient 会缓冲响应,所以并不能证明网络首字节延迟或整体的内存上限。是否惰性求值,要另外用计数生成器来检查。流开始之后如果遇到错误的行,就很难再用正常的错误 JSON 改变状态。真实服务必须确定,在预先验证、按行的错误格式、中止策略之中选择哪一种。
验证行契约
在 /root/work/fa-jsonl-export-lab/service.py 中,validate_row(row) 在它是 dict,id 是排除 bool 的正 int,name 是非空 str 时,返回 row。其余都是 ValueError。允许有额外的内部字段。
先做一次准备。已有的文件不会被覆盖。
mkdir -p /root/work/fa-jsonl-export-lab
test -e /root/work/fa-jsonl-export-lab/service.py || cp /opt/fixtures/ten_labs/fa-jsonl-export-lab/service.py /root/work/fa-jsonl-export-lab/service.py
cd /root/work/fa-jsonl-export-lab
区分 bool 和数字,并把空名称当作错误处理。
保存后用 bash /opt/lab/checks/fa-jsonl-export-lab/01-contract.sh 确认。
只构造公开的行
在 /root/work/fa-jsonl-export-lab/service.py 中,project(row) 在 validate_row 之后,返回只含 id 和 name 的新 dict。原件的内部字段保持原样。
导出路径也必须应用与普通 API 相同的公开字段策略。
保存后用 bash /opt/lab/checks/fa-jsonl-export-lab/02-contract.sh 确认。
保持行边界地编码
在 /root/work/fa-jsonl-export-lab/service.py 中,encode_line(row) 是一个 str:把 project 的结果用 ensure_ascii=False、separators=(',',':')、sort_keys=True 编码为 JSON,并在末尾附加一个 '
'。name 中的换行必须是 JSON 转义。
如果用字符串拼接来生成 JSON,格式会在引号和换行处被破坏。
保存后用 bash /opt/lab/checks/fa-jsonl-export-lab/03-contract.sh 确认。
验证输出数量上限
在 /root/work/fa-jsonl-export-lab/service.py 中,validate_max(value) 只原样返回排除 bool 的 1–1000 的 int,其余都是 ValueError。
要求调用者给出上限,以免意外地把无限输入一直读到底。
保存后用 bash /opt/lab/checks/fa-jsonl-export-lab/04-contract.sh 确认。
只消耗需要的行
在 /root/work/fa-jsonl-export-lab/service.py 中,take_rows(rows, maximum) 是一个 iterator,它借助 islice 等方式,最多只惰性地返回 maximum 条。调用时验证 maximum,每执行一次 next,只消耗一次输入。
一旦转成 list(rows),就无法处理无限输入和大容量输入。
保存后用 bash /opt/lab/checks/fa-jsonl-export-lab/05-contract.sh 确认。
惰性序列化行
在 /root/work/fa-jsonl-export-lab/service.py 中,json_lines(rows, maximum=100) 对从 take_rows 得到的每一行,yield encode_line。不要返回全部拼接好的字符串或列表。
让对象的选择和表示的转换,各自都保持为惰性的步骤。
保存后用 bash /opt/lab/checks/fa-jsonl-export-lab/06-contract.sh 确认。
重新验证下载得到的行
在 /root/work/fa-jsonl-export-lab/service.py 中,decode_lines(text) 对 splitlines 中每一个非空的行,先 json.loads,再 validate_row,然后作为列表返回。空字符串是 [],空的中间行是 ValueError。
区分空文件与格式损坏的空记录。
保存后用 bash /opt/lab/checks/fa-jsonl-export-lab/07-contract.sh 确认。
完成 HTTP 下载
在 /root/work/fa-jsonl-export-lab/service.py 中,create_app(rows) 在 GET /export 中,把 json_lines(rows, 100) 作为 application/x-ndjson 的 StreamingResponse 返回。rows 是可以再次遍历的列表。必须没有内部字段,并保留每一行的内容和顺序。
不能只把 Content-Type 写成流式,还要配合生成器测试,确认内部并没有把全部内容收集起来。
保存后用 bash /opt/lab/checks/fa-jsonl-export-lab/08-contract.sh 确认。