コンテンツにスキップ

pytilpack.fastapi

必要なextra

pip install pytilpack[fastapi]

pytilpack.fastapi

FastAPI関連のユーティリティ。

I18nMiddleware(app, state)

Accept-Languageからロケールを自動設定するASGIミドルウェア。

ソースコード位置: pytilpack/fastapi/i18n.py
def __init__(self, app: starlette.types.ASGIApp, state: pytilpack.i18n.I18nState) -> None:
    self.app = app
    self.state = state

__call__(scope, receive, send) async

ASGIインターフェース。

ソースコード位置: pytilpack/fastapi/i18n.py
async def __call__(
    self,
    scope: starlette.types.Scope,
    receive: starlette.types.Receive,
    send: starlette.types.Send,
) -> None:
    """ASGIインターフェース。"""
    if scope["type"] not in ("http", "websocket"):
        await self.app(scope, receive, send)
        return
    # Accept-Languageからロケールを決定
    headers = starlette.datastructures.Headers(scope=scope)
    accept_lang = headers.get("accept-language", "")
    locale = pytilpack.http.select_accept_language(
        accept_lang,
        self.state.supported_locales,
        default=self.state.default_locale,
    )
    # activate → try/finally → deactivate でリクエストスコープに閉じ込める
    tokens = pytilpack.i18n.activate(self.state, locale)
    try:
        await self.app(scope, receive, send)
    finally:
        pytilpack.i18n.deactivate(tokens)

JSONResponse

Bases: Response

インデント付きJSONを返すFastAPIのresponse_class。

デフォルトのJSONResponseはインデントなしでJSONを返すが、 このクラスはindent=2のインデント付きで返す。 Pydanticモデルやdatetime等もfastapi.encoders.jsonable_encoderで変換する。

Usage

@app.get("/pretty", response_class=pytilpack.fastapi.JSONResponse) def pretty(): return {"message": "indented JSON"}

@app.get("/model", response_model=MyModel, response_class=pytilpack.fastapi.JSONResponse) def model(): return MyModel(field="value")

render(content)

コンテンツをインデント付きJSONのバイト列に変換する。

ソースコード位置: pytilpack/fastapi/misc.py
def render(self, content: typing.Any) -> bytes:
    """コンテンツをインデント付きJSONのバイト列に変換する。"""
    encoded = fastapi.encoders.jsonable_encoder(content)
    return json.dumps(encoded, ensure_ascii=False, indent=2, separators=(", ", ": ")).encode("utf-8")

assert_bytes(response, status_code=200, content_type=None)

レスポンスのステータスコードとContent-Typeを検証してボディをbytesで返す。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200
content_type str | Iterable[str] | None

期待するContent-Type

None

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合

戻り値:

タイプ デスクリプション
bytes

レスポンスボディ

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_bytes(
    response: httpx.Response,
    status_code: int = 200,
    content_type: str | typing.Iterable[str] | None = None,
) -> bytes:
    """レスポンスのステータスコードとContent-Typeを検証してボディをbytesで返す。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード
        content_type: 期待するContent-Type

    Raises:
        AssertionError: ステータスコードが異なる場合

    Returns:
        レスポンスボディ

    """
    response_body = response.content
    _core.assert_bytes_core(response_body, response.status_code, _content_type(response), status_code, content_type)
    return response_body

assert_html(response, status_code=200, content_type='__default__', strict=False, tmp_path=None)

レスポンスを検証してHTMLボディを文字列で返す。html5libが必要。

strict・tmp_pathはキーワード引数での指定を推奨する。flask/quart/fastapi間で 位置引数順を揃えているが、将来の引数追加時の後方互換性を保つためである。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200
content_type str | Iterable[str] | None

期待するContent-Type

'__default__'
strict bool

Trueの場合、HTML5の仕様に従ったパースを行う

False
tmp_path Path | None

一時ファイルを保存するディレクトリ

None

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合

戻り値:

タイプ デスクリプション
str

レスポンスボディ (bs4.BeautifulSoup)

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_html(
    response: httpx.Response,
    status_code: int = 200,
    content_type: str | typing.Iterable[str] | None = "__default__",
    strict: bool = False,
    tmp_path: pathlib.Path | None = None,
) -> str:
    """レスポンスを検証してHTMLボディを文字列で返す。html5libが必要。

    strict・tmp_pathはキーワード引数での指定を推奨する。flask/quart/fastapi間で
    位置引数順を揃えているが、将来の引数追加時の後方互換性を保つためである。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード
        content_type: 期待するContent-Type
        strict: Trueの場合、HTML5の仕様に従ったパースを行う
        tmp_path: 一時ファイルを保存するディレクトリ

    Raises:
        AssertionError: ステータスコードが異なる場合

    Returns:
        レスポンスボディ (bs4.BeautifulSoup)

    """
    response_body = response.text
    _core.assert_html_core(
        response_body,
        response.content,
        response.status_code,
        _content_type(response),
        status_code,
        content_type,
        strict,
        tmp_path,
    )
    return response_body

assert_json(response, status_code=200, content_type='application/json')

レスポンスを検証してJSONをデコードして返す。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200
content_type str | Iterable[str] | None

期待するContent-Type

'application/json'

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合

戻り値:

タイプ デスクリプション
Any

レスポンスのJSONデコード結果

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_json(
    response: httpx.Response,
    status_code: int = 200,
    content_type: str | typing.Iterable[str] | None = "application/json",
) -> typing.Any:
    """レスポンスを検証してJSONをデコードして返す。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード
        content_type: 期待するContent-Type

    Raises:
        AssertionError: ステータスコードが異なる場合

    Returns:
        レスポンスのJSONデコード結果

    """
    response_body = response.text
    return _core.assert_json_core(response_body, response.status_code, _content_type(response), status_code, content_type)

assert_xml(response, status_code=200, content_type='__default__')

レスポンスを検証してXMLボディを文字列で返す。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200
content_type str | Iterable[str] | None

期待するContent-Type

'__default__'

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合

戻り値:

タイプ デスクリプション
str

レスポンスボディ

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_xml(
    response: httpx.Response,
    status_code: int = 200,
    content_type: str | typing.Iterable[str] | None = "__default__",
) -> str:
    """レスポンスを検証してXMLボディを文字列で返す。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード
        content_type: 期待するContent-Type

    Raises:
        AssertionError: ステータスコードが異なる場合

    Returns:
        レスポンスボディ

    """
    response_body = response.text
    _core.assert_xml_core(response_body, response.status_code, _content_type(response), status_code, content_type)
    return response_body

assert_sse(response, status_code=200)

レスポンスのステータスコードとSSE用Content-Typeを検証して返す。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合、またはContent-Typeが異なる場合

戻り値:

タイプ デスクリプション
Response

レスポンス

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_sse(
    response: httpx.Response,
    status_code: int = 200,
) -> httpx.Response:
    """レスポンスのステータスコードとSSE用Content-Typeを検証して返す。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード

    Raises:
        AssertionError: ステータスコードが異なる場合、またはContent-Typeが異なる場合

    Returns:
        レスポンス

    """
    _core.assert_sse_core(response.status_code, _content_type(response), status_code)
    return response

assert_response(response, status_code=200)

レスポンスのステータスコードを検証して返す。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合

戻り値:

タイプ デスクリプション
Response

レスポンス

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_response(
    response: httpx.Response,
    status_code: int = 200,
) -> httpx.Response:
    """レスポンスのステータスコードを検証して返す。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード

    Raises:
        AssertionError: ステータスコードが異なる場合

    Returns:
        レスポンス

    """
    _core.assert_response_core(response.status_code, status_code)
    return response

init_app(app, locale_dir, domain='messages', supported_locales=None, default_locale='en', fallback=True)

FastAPI/Starletteアプリにi18nを統合する。

引数:

名前 タイプ デスクリプション デフォルト
app ASGIApp

FastAPI/Starletteアプリ

必須
locale_dir str | Path

localeディレクトリのパス

必須
domain str

gettextドメイン名

'messages'
supported_locales list[str] | None

サポートするロケール一覧

None
default_locale str

デフォルトロケール

'en'
fallback bool

フォールバック有無

True

戻り値:

タイプ デスクリプション
I18nMiddleware

I18nMiddlewareインスタンス(app.add_middlewareを使う場合はこの関数ではなく

I18nMiddleware

直接I18nMiddlewareを使用する)

ソースコード位置: pytilpack/fastapi/i18n.py
def init_app(
    app: starlette.types.ASGIApp,
    locale_dir: str | pathlib.Path,
    domain: str = "messages",
    supported_locales: list[str] | None = None,
    default_locale: str = "en",
    fallback: bool = True,
) -> I18nMiddleware:
    """FastAPI/Starletteアプリにi18nを統合する。

    Args:
        app: FastAPI/Starletteアプリ
        locale_dir: localeディレクトリのパス
        domain: gettextドメイン名
        supported_locales: サポートするロケール一覧
        default_locale: デフォルトロケール
        fallback: フォールバック有無

    Returns:
        I18nMiddlewareインスタンス(app.add_middlewareを使う場合はこの関数ではなく
        直接I18nMiddlewareを使用する)

    """
    state = pytilpack.i18n.I18nState(
        locale_dir=locale_dir,
        domain=domain,
        locales=supported_locales,
        default_locale=default_locale,
        fallback=fallback,
    )
    return I18nMiddleware(app, state)

asserts

FastAPIのテストコード用アサーション関数。

assert_bytes(response, status_code=200, content_type=None)

レスポンスのステータスコードとContent-Typeを検証してボディをbytesで返す。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200
content_type str | Iterable[str] | None

期待するContent-Type

None

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合

戻り値:

タイプ デスクリプション
bytes

レスポンスボディ

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_bytes(
    response: httpx.Response,
    status_code: int = 200,
    content_type: str | typing.Iterable[str] | None = None,
) -> bytes:
    """レスポンスのステータスコードとContent-Typeを検証してボディをbytesで返す。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード
        content_type: 期待するContent-Type

    Raises:
        AssertionError: ステータスコードが異なる場合

    Returns:
        レスポンスボディ

    """
    response_body = response.content
    _core.assert_bytes_core(response_body, response.status_code, _content_type(response), status_code, content_type)
    return response_body

assert_html(response, status_code=200, content_type='__default__', strict=False, tmp_path=None)

レスポンスを検証してHTMLボディを文字列で返す。html5libが必要。

strict・tmp_pathはキーワード引数での指定を推奨する。flask/quart/fastapi間で 位置引数順を揃えているが、将来の引数追加時の後方互換性を保つためである。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200
content_type str | Iterable[str] | None

期待するContent-Type

'__default__'
strict bool

Trueの場合、HTML5の仕様に従ったパースを行う

False
tmp_path Path | None

一時ファイルを保存するディレクトリ

None

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合

戻り値:

タイプ デスクリプション
str

レスポンスボディ (bs4.BeautifulSoup)

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_html(
    response: httpx.Response,
    status_code: int = 200,
    content_type: str | typing.Iterable[str] | None = "__default__",
    strict: bool = False,
    tmp_path: pathlib.Path | None = None,
) -> str:
    """レスポンスを検証してHTMLボディを文字列で返す。html5libが必要。

    strict・tmp_pathはキーワード引数での指定を推奨する。flask/quart/fastapi間で
    位置引数順を揃えているが、将来の引数追加時の後方互換性を保つためである。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード
        content_type: 期待するContent-Type
        strict: Trueの場合、HTML5の仕様に従ったパースを行う
        tmp_path: 一時ファイルを保存するディレクトリ

    Raises:
        AssertionError: ステータスコードが異なる場合

    Returns:
        レスポンスボディ (bs4.BeautifulSoup)

    """
    response_body = response.text
    _core.assert_html_core(
        response_body,
        response.content,
        response.status_code,
        _content_type(response),
        status_code,
        content_type,
        strict,
        tmp_path,
    )
    return response_body

assert_json(response, status_code=200, content_type='application/json')

レスポンスを検証してJSONをデコードして返す。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200
content_type str | Iterable[str] | None

期待するContent-Type

'application/json'

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合

戻り値:

タイプ デスクリプション
Any

レスポンスのJSONデコード結果

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_json(
    response: httpx.Response,
    status_code: int = 200,
    content_type: str | typing.Iterable[str] | None = "application/json",
) -> typing.Any:
    """レスポンスを検証してJSONをデコードして返す。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード
        content_type: 期待するContent-Type

    Raises:
        AssertionError: ステータスコードが異なる場合

    Returns:
        レスポンスのJSONデコード結果

    """
    response_body = response.text
    return _core.assert_json_core(response_body, response.status_code, _content_type(response), status_code, content_type)

assert_xml(response, status_code=200, content_type='__default__')

レスポンスを検証してXMLボディを文字列で返す。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200
content_type str | Iterable[str] | None

期待するContent-Type

'__default__'

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合

戻り値:

タイプ デスクリプション
str

レスポンスボディ

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_xml(
    response: httpx.Response,
    status_code: int = 200,
    content_type: str | typing.Iterable[str] | None = "__default__",
) -> str:
    """レスポンスを検証してXMLボディを文字列で返す。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード
        content_type: 期待するContent-Type

    Raises:
        AssertionError: ステータスコードが異なる場合

    Returns:
        レスポンスボディ

    """
    response_body = response.text
    _core.assert_xml_core(response_body, response.status_code, _content_type(response), status_code, content_type)
    return response_body

assert_sse(response, status_code=200)

レスポンスのステータスコードとSSE用Content-Typeを検証して返す。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合、またはContent-Typeが異なる場合

戻り値:

タイプ デスクリプション
Response

レスポンス

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_sse(
    response: httpx.Response,
    status_code: int = 200,
) -> httpx.Response:
    """レスポンスのステータスコードとSSE用Content-Typeを検証して返す。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード

    Raises:
        AssertionError: ステータスコードが異なる場合、またはContent-Typeが異なる場合

    Returns:
        レスポンス

    """
    _core.assert_sse_core(response.status_code, _content_type(response), status_code)
    return response

assert_response(response, status_code=200)

レスポンスのステータスコードを検証して返す。

引数:

名前 タイプ デスクリプション デフォルト
response Response

レスポンス

必須
status_code int

期待するステータスコード

200

発生:

タイプ デスクリプション
AssertionError

ステータスコードが異なる場合

戻り値:

タイプ デスクリプション
Response

レスポンス

ソースコード位置: pytilpack/fastapi/asserts.py
def assert_response(
    response: httpx.Response,
    status_code: int = 200,
) -> httpx.Response:
    """レスポンスのステータスコードを検証して返す。

    Args:
        response: レスポンス
        status_code: 期待するステータスコード

    Raises:
        AssertionError: ステータスコードが異なる場合

    Returns:
        レスポンス

    """
    _core.assert_response_core(response.status_code, status_code)
    return response

i18n

FastAPI用i18n統合。

I18nMiddleware(app, state)

Accept-Languageからロケールを自動設定するASGIミドルウェア。

ソースコード位置: pytilpack/fastapi/i18n.py
def __init__(self, app: starlette.types.ASGIApp, state: pytilpack.i18n.I18nState) -> None:
    self.app = app
    self.state = state
__call__(scope, receive, send) async

ASGIインターフェース。

ソースコード位置: pytilpack/fastapi/i18n.py
async def __call__(
    self,
    scope: starlette.types.Scope,
    receive: starlette.types.Receive,
    send: starlette.types.Send,
) -> None:
    """ASGIインターフェース。"""
    if scope["type"] not in ("http", "websocket"):
        await self.app(scope, receive, send)
        return
    # Accept-Languageからロケールを決定
    headers = starlette.datastructures.Headers(scope=scope)
    accept_lang = headers.get("accept-language", "")
    locale = pytilpack.http.select_accept_language(
        accept_lang,
        self.state.supported_locales,
        default=self.state.default_locale,
    )
    # activate → try/finally → deactivate でリクエストスコープに閉じ込める
    tokens = pytilpack.i18n.activate(self.state, locale)
    try:
        await self.app(scope, receive, send)
    finally:
        pytilpack.i18n.deactivate(tokens)

init_app(app, locale_dir, domain='messages', supported_locales=None, default_locale='en', fallback=True)

FastAPI/Starletteアプリにi18nを統合する。

引数:

名前 タイプ デスクリプション デフォルト
app ASGIApp

FastAPI/Starletteアプリ

必須
locale_dir str | Path

localeディレクトリのパス

必須
domain str

gettextドメイン名

'messages'
supported_locales list[str] | None

サポートするロケール一覧

None
default_locale str

デフォルトロケール

'en'
fallback bool

フォールバック有無

True

戻り値:

タイプ デスクリプション
I18nMiddleware

I18nMiddlewareインスタンス(app.add_middlewareを使う場合はこの関数ではなく

I18nMiddleware

直接I18nMiddlewareを使用する)

ソースコード位置: pytilpack/fastapi/i18n.py
def init_app(
    app: starlette.types.ASGIApp,
    locale_dir: str | pathlib.Path,
    domain: str = "messages",
    supported_locales: list[str] | None = None,
    default_locale: str = "en",
    fallback: bool = True,
) -> I18nMiddleware:
    """FastAPI/Starletteアプリにi18nを統合する。

    Args:
        app: FastAPI/Starletteアプリ
        locale_dir: localeディレクトリのパス
        domain: gettextドメイン名
        supported_locales: サポートするロケール一覧
        default_locale: デフォルトロケール
        fallback: フォールバック有無

    Returns:
        I18nMiddlewareインスタンス(app.add_middlewareを使う場合はこの関数ではなく
        直接I18nMiddlewareを使用する)

    """
    state = pytilpack.i18n.I18nState(
        locale_dir=locale_dir,
        domain=domain,
        locales=supported_locales,
        default_locale=default_locale,
        fallback=fallback,
    )
    return I18nMiddleware(app, state)

misc

FastAPI関連のその他のユーティリティ。

JSONResponse

Bases: Response

インデント付きJSONを返すFastAPIのresponse_class。

デフォルトのJSONResponseはインデントなしでJSONを返すが、 このクラスはindent=2のインデント付きで返す。 Pydanticモデルやdatetime等もfastapi.encoders.jsonable_encoderで変換する。

Usage

@app.get("/pretty", response_class=pytilpack.fastapi.JSONResponse) def pretty(): return {"message": "indented JSON"}

@app.get("/model", response_model=MyModel, response_class=pytilpack.fastapi.JSONResponse) def model(): return MyModel(field="value")

render(content)

コンテンツをインデント付きJSONのバイト列に変換する。

ソースコード位置: pytilpack/fastapi/misc.py
def render(self, content: typing.Any) -> bytes:
    """コンテンツをインデント付きJSONのバイト列に変換する。"""
    encoded = fastapi.encoders.jsonable_encoder(content)
    return json.dumps(encoded, ensure_ascii=False, indent=2, separators=(", ", ": ")).encode("utf-8")