TT Lab
はじめる
学ぶ 学習パス コース

FastAPI — 型がそのまま契約だ

全行をためずにJSON Linesを出力する

TT Labで続きを見る

目標

遅延生成・行の境界・公開フィールド・出力の上限を、HTTPのストリームにつなげます。

なぜ重要なのか

管理者がすべての注文をダウンロードすると、サーバーのメモリが急増しました。export関数が、JSONを作る前に、すべての行をリストに集めたからです。行単位の出力に変えたものの、本文の中の改行が実際の行の境界を壊し、内部の原価もそのまま出ていきました。ストリーミングは、戻り値の型を1つ変える作業ではなく、遅延評価と表現の契約を一緒に決める作業です。

ステップ

  1. /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
  1. /root/work/fa-jsonl-export-lab/service.pyで、project(row)は、validate_rowのあとで、idとnameだけを持つ新しいdictを返します。元の内部フィールドは、そのまま保存します。

  2. /root/work/fa-jsonl-export-lab/service.pyで、encode_line(row)は、projectの結果を、ensure_ascii=False、separators=(',',':')、sort_keys=TrueでJSONエンコードして、最後に' 'を1つ付けたstrです。nameの中の改行は、JSONのエスケープでなければなりません。

  3. /root/work/fa-jsonl-export-lab/service.pyで、validate_max(value)は、boolを除くintの1–1000だけをそのまま返し、それ以外はValueErrorです。

  4. /root/work/fa-jsonl-export-lab/service.pyで、take_rows(rows, maximum)は、isliceなどで最大maximum個だけを遅延して返すiteratorです。呼び出したときにmaximumを検証し、1回nextすると、入力を1回だけ消費します。

  5. /root/work/fa-jsonl-export-lab/service.pyで、json_lines(rows, maximum=100)は、take_rowsから受け取った行ごとに、encode_lineをyieldします。すべてをまとめた文字列やリストは返しません。

  6. /root/work/fa-jsonl-export-lab/service.pyで、decode_lines(text)は、splitlinesの空でない各行を、json.loadsしたあとでvalidate_rowして、リストとして返します。空の文字列は[]、空である途中の行は、ValueErrorです。

  7. /root/work/fa-jsonl-export-lab/service.pyで、create_app(rows)は、GET /exportで、json_lines(rows, 100)を、application/x-ndjsonのStreamingResponseとして返します。rowsは、再び走査できるリストです。内部フィールドがなく、各行の内容と順序を保存する必要があります。

参考

行の契約を検証する

/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)は、projectの結果を、ensure_ascii=False、separators=(',',':')、sort_keys=TrueでJSONエンコードして、最後に' 'を1つ付けたstrです。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を除くintの1–1000だけをそのまま返し、それ以外はValueErrorです。

無限の入力を、誤って最後まで読まないように、呼び出し側に上限を要求します。

保存したら、bash /opt/lab/checks/fa-jsonl-export-lab/04-contract.shで確認してください。

必要な行だけを消費する

/root/work/fa-jsonl-export-lab/service.pyで、take_rows(rows, maximum)は、isliceなどで最大maximum個だけを遅延して返すiteratorです。呼び出したときにmaximumを検証し、1回nextすると、入力を1回だけ消費します。

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から受け取った行ごとに、encode_lineをyieldします。すべてをまとめた文字列やリストは返しません。

オブジェクトの選択と、表現の変換を、それぞれ遅延のステップとして維持します。

保存したら、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で確認してください。