全行をためずにJSON Linesを出力する
目標
遅延生成・行の境界・公開フィールド・出力の上限を、HTTPのストリームにつなげます。
なぜ重要なのか
管理者がすべての注文をダウンロードすると、サーバーのメモリが急増しました。export関数が、JSONを作る前に、すべての行をリストに集めたからです。行単位の出力に変えたものの、本文の中の改行が実際の行の境界を壊し、内部の原価もそのまま出ていきました。ストリーミングは、戻り値の型を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
-
/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)は、projectの結果を、ensure_ascii=False、separators=(',',':')、sort_keys=TrueでJSONエンコードして、最後に' 'を1つ付けたstrです。nameの中の改行は、JSONのエスケープでなければなりません。 -
/root/work/fa-jsonl-export-lab/service.pyで、validate_max(value)は、boolを除くintの1–1000だけをそのまま返し、それ以外はValueErrorです。 -
/root/work/fa-jsonl-export-lab/service.pyで、take_rows(rows, maximum)は、isliceなどで最大maximum個だけを遅延して返すiteratorです。呼び出したときにmaximumを検証し、1回nextすると、入力を1回だけ消費します。 -
/root/work/fa-jsonl-export-lab/service.pyで、json_lines(rows, maximum=100)は、take_rowsから受け取った行ごとに、encode_lineをyieldします。すべてをまとめた文字列やリストは返しません。 -
/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)は、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で確認してください。