抜け・重複のないカーソルページネーション
目標
ソート・フィルター・カーソル・レスポンスの射影を分離して、FastAPIのリクエストにもう一度つなげます。
なぜ重要なのか
正常なリクエストが1回成功しても、境界値や障害からの復旧は保証されません。このラボは、各関数の契約を小さく実装またはテストしたあと、実際の実行につなげます。コードが存在するかどうかやレポートの文言だけを見るのではなく、結果・例外・保存された状態を検査します。前のステップのコードを維持しながら、次のステップへ進んでください。
ステップ
/root/work/fa-pagination-lab/service.pyで、parse_limit(value)は、文字列の整数を受け取って、1から50ならintを返し、それ以外はValueErrorで拒否してください。最初の準備は、次のコマンドで行います。
mkdir -p /root/work/fa-pagination-lab
cp /opt/fixtures/practice_depth/fa-pagination-lab/* /root/work/fa-pagination-lab/
cd /root/work/fa-pagination-lab
/root/work/fa-pagination-lab/service.pyで、ordered_rows(rows)は、id昇順の新しいリストを返してください。入力のrowsは変更しません。各行は、一意の正の整数のidと、name、category、internal_costを持ちます。/root/work/fa-pagination-lab/service.pyで、filter_rows(rows, category=None)は、categoryがNoneなら全体を、指定されていれば、まったく同じカテゴリだけを元の順序で返してください。存在しないカテゴリは、空のリストです。/root/work/fa-pagination-lab/service.pyで、encode_cursor(item_id)は、正の整数のidの10進数の文字列をUTF-8に変換したあと、paddingを含むURL-safeなbase64の文字列を返してください。/root/work/fa-pagination-lab/service.pyで、decode_cursor(token)は、上のカーソルを正の整数に復元してください。不正なbase64、空の値、0・負の数・数字ではない値は、ValueErrorです。ASCIIの10進数の数字だけを許可し、改行や余分な文字を無視しないでください。/root/work/fa-pagination-lab/service.pyで、page_after(rows, after, limit)は、id > afterの行を昇順でlimit件まで返してください。戻り値は(page, next_cursor)で、残りの行があるときだけ、pageの最後のidをエンコードします。空の一覧と、終わりではNoneです。/root/work/fa-pagination-lab/service.pyで、public_item(row)は、id、name、categoryだけを入れた新しい辞書を返してください。元のinternal_costは削除も変更もしません。/root/work/fa-pagination-lab/service.pyで、create_app(rows)は、GET /itemsを持つFastAPIアプリを返してください。limitの既定は10、afterの既定はなし、categoryの既定はなしです。前の関数を組み合わせて{items: [...], next_cursor: 文字列またはnull}を返し、不正なlimitとafterには422で応答してください。
参考
- パッケージはイメージにインストールされています。インターネット接続やpip installは必要ありません。
- 準備のコピーは、最初の1回だけ行ってください。もう一度コピーすると、作業ファイルが初期化されます。
- 採点は、皆さんのservice.pyを読み込んで、別のidと一時的なストレージで実行します。結果を定数でハードコードしないでください。
- 各ステップの採点は、45秒以内で実行されます。無限ループや、実際の待機を作らないでください。
limitの範囲を契約として固定する
/root/work/fa-pagination-lab/service.pyで、parse_limit(value)は、文字列の整数を受け取って、1から50ならintを返し、それ以外はValueErrorで拒否してください。最初の準備は、次のコマンドで行います。
mkdir -p /root/work/fa-pagination-lab
cp /opt/fixtures/practice_depth/fa-pagination-lab/* /root/work/fa-pagination-lab/
cd /root/work/fa-pagination-lab
intへの変換エラーを隠さず、変換したあとで、下限と上限を一緒に検査します。
採点は、bash /opt/lab/checks/fa-pagination-lab/01-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
入力の順序とレスポンスの順序を分離する
/root/work/fa-pagination-lab/service.pyで、ordered_rows(rows)は、id昇順の新しいリストを返してください。入力のrowsは変更しません。各行は、一意の正の整数のidと、name、category、internal_costを持ちます。
list.sortは元のリストを変更します。sortedとkey関数を比べてみてください。
採点は、bash /opt/lab/checks/fa-pagination-lab/02-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
ページよりも先にフィルターを適用する
/root/work/fa-pagination-lab/service.pyで、filter_rows(rows, category=None)は、categoryがNoneなら全体を、指定されていれば、まったく同じカテゴリだけを元の順序で返してください。存在しないカテゴリは、空のリストです。
先に2件を切り出してからカテゴリで絞り込むと、空のページと欠落が生まれます。
採点は、bash /opt/lab/checks/fa-pagination-lab/03-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
最後のidをカーソルとして表現する
/root/work/fa-pagination-lab/service.pyで、encode_cursor(item_id)は、正の整数のidの10進数の文字列をUTF-8に変換したあと、paddingを含むURL-safeなbase64の文字列を返してください。
base64.urlsafe_b64encodeはbytesを受け取ります。返されたbytesも、文字列に変換します。セキュリティトークンだと解釈しないでください。
採点は、bash /opt/lab/checks/fa-pagination-lab/04-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
壊れたカーソルを早めに拒否する
/root/work/fa-pagination-lab/service.pyで、decode_cursor(token)は、上のカーソルを正の整数に復元してください。不正なbase64、空の値、0・負の数・数字ではない値は、ValueErrorです。ASCIIの10進数の数字だけを許可し、改行や余分な文字を無視しないでください。
b64decodeのaltcharsとvalidate=Trueを活用し、デコードしたあとにも、値のドメインを検査します。
採点は、bash /opt/lab/checks/fa-pagination-lab/05-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
最後のページを正確に区別する
/root/work/fa-pagination-lab/service.pyで、page_after(rows, after, limit)は、id > afterの行を昇順でlimit件まで返してください。戻り値は(page, next_cursor)で、残りの行があるときだけ、pageの最後のidをエンコードします。空の一覧と、終わりではNoneです。
limit件を得たという事実と、次の行が存在するという事実は、別です。比較は>=ではなく>です。
採点は、bash /opt/lab/checks/fa-pagination-lab/06-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
レスポンスから内部フィールドを取り除く
/root/work/fa-pagination-lab/service.pyで、public_item(row)は、id、name、categoryだけを入れた新しい辞書を返してください。元のinternal_costは削除も変更もしません。
拒否リストよりも、許可リストで新しいオブジェクトを作るほうが、新しい内部フィールドが追加されても安全です。
採点は、bash /opt/lab/checks/fa-pagination-lab/07-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
実際のHTTPの契約として合わせる
/root/work/fa-pagination-lab/service.pyで、create_app(rows)は、GET /itemsを持つFastAPIアプリを返してください。limitの既定は10、afterの既定はなし、categoryの既定はなしです。前の関数を組み合わせて{items: [...], next_cursor: 文字列またはnull}を返し、不正なlimitとafterには422で応答してください。
TestClient(create_app(rows))でリクエストします。フィルター → カーソルによるページ → 公開フィールドの順序を守り、ValueErrorだけをHTTPエラーに変換してください。
採点は、bash /opt/lab/checks/fa-pagination-lab/08-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。