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

FastAPI — 型がそのまま契約だ

抜け・重複のないカーソルページネーション

TT Labで続きを見る

目標

ソート・フィルター・カーソル・レスポンスの射影を分離して、FastAPIのリクエストにもう一度つなげます。

なぜ重要なのか

正常なリクエストが1回成功しても、境界値や障害からの復旧は保証されません。このラボは、各関数の契約を小さく実装またはテストしたあと、実際の実行につなげます。コードが存在するかどうかやレポートの文言だけを見るのではなく、結果・例外・保存された状態を検査します。前のステップのコードを維持しながら、次のステップへ進んでください。

ステップ

  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
  1. /root/work/fa-pagination-lab/service.pyで、ordered_rows(rows)は、id昇順の新しいリストを返してください。入力のrowsは変更しません。各行は、一意の正の整数のidと、name、category、internal_costを持ちます。
  2. /root/work/fa-pagination-lab/service.pyで、filter_rows(rows, category=None)は、categoryがNoneなら全体を、指定されていれば、まったく同じカテゴリだけを元の順序で返してください。存在しないカテゴリは、空のリストです。
  3. /root/work/fa-pagination-lab/service.pyで、encode_cursor(item_id)は、正の整数のidの10進数の文字列をUTF-8に変換したあと、paddingを含むURL-safeなbase64の文字列を返してください。
  4. /root/work/fa-pagination-lab/service.pyで、decode_cursor(token)は、上のカーソルを正の整数に復元してください。不正なbase64、空の値、0・負の数・数字ではない値は、ValueErrorです。ASCIIの10進数の数字だけを許可し、改行や余分な文字を無視しないでください。
  5. /root/work/fa-pagination-lab/service.pyで、page_after(rows, after, limit)は、id > afterの行を昇順でlimit件まで返してください。戻り値は(page, next_cursor)で、残りの行があるときだけ、pageの最後のidをエンコードします。空の一覧と、終わりではNoneです。
  6. /root/work/fa-pagination-lab/service.pyで、public_item(row)は、id、name、categoryだけを入れた新しい辞書を返してください。元のinternal_costは削除も変更もしません。
  7. /root/work/fa-pagination-lab/service.pyで、create_app(rows)は、GET /itemsを持つFastAPIアプリを返してください。limitの既定は10、afterの既定はなし、categoryの既定はなしです。前の関数を組み合わせて{items: [...], next_cursor: 文字列またはnull}を返し、不正なlimitとafterには422で応答してください。

参考

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で直接再現できます。ファイルを保存してから、もう一度実行してください。