Skip to content

Item Handler

Item handler は、ItemType ごとの API を定義します。 また、sonolus.items.<item_type> から保存済みアイテムを扱う item store にアクセスできます。

パスは基本形 /sonolus/... で記載しています。 APIRouter(prefix="/api") で統合した場合は /api/sonolus/... になります。

info

アイテムのinfoを提供します。

path: GET /sonolus/{item_type}/info

Registration

py
from sonolus_fastapi import Sonolus
from sonolus_models import ServerItemInfo

sonolus = Sonolus(
    address='https://example.com',
    port=8000,
    enable_cors=True,
    dev=True,
)

@sonolus.level.info(ServerItemInfo) # 例でlevelとしたが、level以外も可

list

アイテムのlistを提供します

path: GET /sonolus/{item_type}/list

Registration

py
from sonolus_fastapi import Sonolus
from sonolus_models import ServerItemList

sonolus = Sonolus(
    address='https://example.com',
    port=8000,
    enable_cors=True,
    dev=True,
)

@sonolus.level.list(ServerItemList) # 例でlevelとしたが、level以外も可

detail

アイテムの詳細情報を提供します

path: GET /sonolus/{item_type}/{item_name}

Registration

py
from sonolus_fastapi import Sonolus
from sonolus_models import ServerItemDetails

sonolus = Sonolus(
    address='https://example.com',
    port=8000,
    enable_cors=True,
    dev=True,
)

@sonolus.level.detail(ServerItemDetails) # 例でlevelとしたが、level以外も可
async def level_detail(ctx, name: str):
    # nameでアイテムを取得
    level = sonolus.items.level.get(name)
    return ServerItemDetails(
        item=level,
        description="Level description",
        actions=None,
        hasCommunity=False,
        leaderboards=[],
        sections=[]
    )

source フィールドについて

  • item.source は保存時にストレージへ永続化されません
  • レスポンス返却時に Sonolus.address(未設定時はリクエストURL)で動的上書きされます
  • 開発/本番でURLが変わっても、データ移行なしで柔軟に対応できます

TaggableItem

sonolus.items.<item_type>.get(name) で取得したアイテムは、TaggableItem として返されます。

TaggableItemLevelItemPostItem などの Pydantic モデルをラップし、tags をタイトル文字列で扱うための補助メソッドを追加します。 ラップされているだけなので、通常のアイテム属性にはそのままアクセスできます。

TIP

TaggableItem の各操作メソッドは、元のアイテムを直接書き換えず、タグを変更した新しいアイテムモデルを返します。 ストレージへ反映する場合は、返されたアイテムを update() してください。

Supported stores

TaggableItem は、item store が有効な場合に利用できます。

  • memory
  • json
  • database

対象は sonolus.items.level.get(name)sonolus.items.post.get(name) など、各 item store の get() です。 list() で返るアイテム一覧は通常のアイテムモデルです。

Methods

MethodDescription
with_tags(tag_titles)指定したタグで上書きした新しいアイテムを返します
add_tags(tag_titles)既存タグに追加した新しいアイテムを返します。同じタイトルのタグは重複追加されません
remove_tags(tag_titles)指定したタイトルのタグを削除した新しいアイテムを返します
clear_tags()タグを空にした新しいアイテムを返します
get_tag_titles()現在のタグタイトル一覧を返します

tag_titles には str のリストを渡します。 内部では sonolus_models.Tag(title=...) が自動で作成されます。

Example: タグを上書きする

py
item = sonolus.items.post.get("example-post-1")

if item is None:
    raise ValueError("Item not found")

updated_item = item.with_tags(["news", "announcement"])
sonolus.items.post.update(updated_item)

Example: 既存タグに追加する

py
item = sonolus.items.level.get("example-level")

if item is None:
    raise ValueError("Item not found")

updated_item = item.add_tags(["featured", "beginner"])
sonolus.items.level.update(updated_item)

add_tags() は既存タグのタイトルを確認し、同じタイトルがある場合は追加しません。

Example: レスポンス時だけタグを付ける

ストレージには保存せず、handler のレスポンスだけタグを変更したい場合は、update() を呼ばずに返します。

py
from sonolus_models import ServerItemDetails

@sonolus.level.detail(ServerItemDetails)
async def level_detail(ctx, name: str):
    item = sonolus.items.level.get(name)

    if item is None:
        raise ValueError("Item not found")

    level = item.add_tags(["preview"])

    return ServerItemDetails(
        item=level,
        description="Level description",
        actions=None,
        hasCommunity=False,
        leaderboards=[],
        sections=[],
    )

Example: タグの確認と削除

py
item = sonolus.items.post.get("example-post-1")

if item is None:
    raise ValueError("Item not found")

current_tags = item.get_tag_titles()

if "draft" in current_tags:
    updated_item = item.remove_tags(["draft"])
    sonolus.items.post.update(updated_item)

Notes

WARNING

with_tags()add_tags()remove_tags()clear_tags() の戻り値は TaggableItem ではなく、通常のアイテムモデルです。 続けてタグ操作を行う場合は、保存後に再度 get() するか、返されたモデルの tags を直接扱ってください。

属性アクセスについて

TaggableItem はラップ元アイテムへの透過的な属性アクセスを提供します。

py
item = sonolus.items.post.get("example-post-1")

if item is not None:
    print(item.name)
    print(item.title)
    print(item.tags)

actions

アイテムのアクション(submit)を処理します

path: POST /sonolus/{item_type}/{item_name}/submit

Registration

py
from sonolus_fastapi import Sonolus
from sonolus_models import ServerSubmitItemActionResponse

sonolus = Sonolus(
    address='https://example.com',
    port=8000,
    enable_cors=True,
    dev=True,
)

@sonolus.level.actions(ServerSubmitItemActionResponse)
async def level_actions(ctx, name: str, action_request):
    # action_requestにはフォームの値が入っている
    # アクションに応じた処理を実行
    return ServerSubmitItemActionResponse(
        shouldUpdateItem=True,
        shouldRemoveItem=False,
        key="upload-key-123",  # ファイルアップロードが必要な場合
        hashes=[]  # アップロードが必要なファイルのハッシュ
    )

upload

アイテムのファイルアップロードを処理します

path: POST /sonolus/{item_type}/{item_name}/upload

Registration

py
from sonolus_fastapi import Sonolus
from sonolus_models import ServerUploadItemActionResponse

sonolus = Sonolus(
    address='https://example.com',
    port=8000,
    enable_cors=True,
    dev=True,
)

@sonolus.level.upload(ServerUploadItemActionResponse)
async def level_upload(ctx, name: str, upload_key: str, files: list):
    # upload_key: actions時に返したkey
    # files: アップロードされたファイルのリスト
    for file in files:
        filename = file.filename
        content = await file.read()
        # ファイルを保存
    
    return ServerUploadItemActionResponse(
        shouldUpdateItem=True
    )