pytilpack.quart¶
必要なextra
pip install pytilpack[quart]
pytilpack.quart
¶
Quart関連のユーティリティ。
ResponseType = quart.Response | typing.Awaitable[quart.Response]
module-attribute
¶
レスポンスの型。
ConcurrencyState(semaphore, max_concurrency, timeout)
dataclass
¶
set_max_concurrency の内部状態。exhaust_concurrency から参照される。
ProxyFix(quartapp, x_for=1, x_proto=1, x_host=0, x_port=0, x_prefix=1, static_prefix=None, trusted_hosts=None)
¶
リバースプロキシ対応。
nginx.conf設定例::
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Prefix $http_x_forwarded_prefix;
参考
- hypercorn.middleware.ProxyFixMiddleware https://github.com/pgjones/hypercorn/blob/main/src/hypercorn/middleware/proxy_fix.py
設計意図
prefixはpin方式で管理する。初回有効リクエスト時に一度だけapp.configへ反映し、
以降は変更しない。これにより攻撃者制御のX-Forwarded-Prefix値で
Cookie発行パス等のアプリ全体設定が書き換わる経路を断つ。
運用時にprefixが既知であれば、static_prefix引数で起動時に確定させることを推奨する。
static_prefixを指定した場合、初期化時にapp.configを確定する。
ただしscope["root_path"]はリクエストごとにX-Forwarded-Prefixヘッダーの値から
設定するため、ヘッダーが来ないリクエストではscope["root_path"]は更新されない。
x_host > 0でX-Forwarded-Hostを有効化する場合は、trusted_hosts引数で
許可ホストリストを指定することを推奨する。指定がない場合は値検証(制御文字・
//始まり等の排除)のみ行い追加警告は出力しない。
旧挙動(リクエストごとにapp.configを上書き)を期待していた利用者は、
static_prefix指定方式、またはpin後の挙動(初回リクエストでpinされる)へ移行すること。
ソースコード位置: pytilpack/quart/proxy_fix.py
__call__(scope, receive, send)
async
¶
ASGIアプリケーションとしての処理。
ソースコード位置: pytilpack/quart/proxy_fix.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | |
assert_bytes(response, status_code=200, content_type=None)
async
¶
レスポンスのステータスコードとContent-Typeを検証してボディをbytesで返す。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
status_code
|
int
|
期待するステータスコード |
200
|
content_type
|
str | Iterable[str] | None
|
期待するContent-Type |
None
|
発生:
| タイプ | デスクリプション |
|---|---|
AssertionError
|
ステータスコードが異なる場合など |
戻り値:
| タイプ | デスクリプション |
|---|---|
bytes
|
レスポンスボディ |
ソースコード位置: pytilpack/quart/asserts.py
assert_html(response, status_code=200, content_type='__default__', strict=False, tmp_path=None)
async
¶
レスポンスを検証してHTMLボディを文字列で返す。html5libが必要。
strict・tmp_pathはキーワード引数での指定を推奨する。flask/quart/fastapi間で 位置引数順を揃えているが、将来の引数追加時の後方互換性を保つためである。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
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/quart/asserts.py
assert_json(response, status_code=200, content_type='application/json')
async
¶
レスポンスを検証してJSONをデコードして返す。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
status_code
|
int
|
期待するステータスコード |
200
|
content_type
|
str | Iterable[str] | None
|
期待するContent-Type |
'application/json'
|
発生:
| タイプ | デスクリプション |
|---|---|
AssertionError
|
ステータスコードが異なる場合など |
戻り値:
| タイプ | デスクリプション |
|---|---|
Any
|
レスポンスのJSONデコード結果 |
ソースコード位置: pytilpack/quart/asserts.py
assert_xml(response, status_code=200, content_type='__default__')
async
¶
レスポンスを検証してXMLボディを文字列で返す。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
status_code
|
int
|
期待するステータスコード |
200
|
content_type
|
str | Iterable[str] | None
|
期待するContent-Type |
'__default__'
|
発生:
| タイプ | デスクリプション |
|---|---|
AssertionError
|
ステータスコードが異なる場合など |
戻り値:
| タイプ | デスクリプション |
|---|---|
str
|
レスポンスボディ |
ソースコード位置: pytilpack/quart/asserts.py
assert_sse(response, status_code=200)
async
¶
レスポンスのステータスコードとSSE用Content-Typeを検証して返す。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
status_code
|
int
|
期待するステータスコード |
200
|
発生:
| タイプ | デスクリプション |
|---|---|
AssertionError
|
ステータスコードが異なる場合、またはContent-Typeが異なる場合 |
戻り値:
| タイプ | デスクリプション |
|---|---|
Response
|
レスポンス |
ソースコード位置: pytilpack/quart/asserts.py
assert_response(response, status_code=200)
async
¶
レスポンスのステータスコードを検証して返す。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
status_code
|
int
|
期待するステータスコード |
200
|
発生:
| タイプ | デスクリプション |
|---|---|
AssertionError
|
ステータスコードが異なる場合 |
戻り値:
| タイプ | デスクリプション |
|---|---|
Response
|
レスポンス |
ソースコード位置: pytilpack/quart/asserts.py
init_app(app, locale_dir, domain='messages', supported_locales=None, default_locale='en', fallback=True)
¶
Quartアプリにi18nを統合する。
before_requestでAccept-Languageからロケールを自動設定し、 Jinja2テンプレートに翻訳関数を登録する。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
app
|
Quart
|
Quartアプリ |
必須 |
locale_dir
|
str | Path
|
localeディレクトリのパス |
必須 |
domain
|
str
|
gettextドメイン名 |
'messages'
|
supported_locales
|
list[str] | None
|
サポートするロケール一覧 |
None
|
default_locale
|
str
|
デフォルトロケール |
'en'
|
fallback
|
bool
|
フォールバック有無 |
True
|
ソースコード位置: pytilpack/quart/i18n.py
set_max_concurrency(app, max_concurrency, timeout=3.0)
¶
Quart アプリ全体の最大同時リクエスト数を制限する。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
app
|
Quart
|
対象の Quart アプリケーション。 |
必須 |
max_concurrency
|
int
|
許可する同時リクエスト数の上限。 |
必須 |
timeout
|
float | None
|
最大待機秒数。タイムアウト時は 503 Service Unavailable を返す。 |
3.0
|
発生:
| タイプ | デスクリプション |
|---|---|
ValueError
|
max_concurrency が 1 未満の場合。 |
RuntimeError
|
同一アプリに対して二度目に呼び出した場合。 before_request/teardown_request が重複登録されてセマフォが二重管理されるため、 同一アプリへの複数回呼び出しは禁止する。 |
Notes
- Hypercorn の
--workers/--threadsとは独立した アプリレベルの制御。1 ワーカー内のコルーチン数を制限する用途で使う。
ソースコード位置: pytilpack/quart/misc.py
exhaust_concurrency(app)
async
¶
テスト用: セマフォを枯渇させて503を発生させるコンテキストマネージャ。
使用例::
async with pytilpack.quart.exhaust_concurrency(app):
response = await client.get("/any-endpoint")
assert response.status_code == 503
ソースコード位置: pytilpack/quart/misc.py
run_sync(func)
¶
同期関数を非同期に実行するデコレーター。
quart.utils.run_syncの型ヒントがいまいちなので用意。
ソースコード位置: pytilpack/quart/misc.py
get_next_url()
¶
prefer_markdown()
¶
AcceptヘッダーでmarkdownがHTMLより優先されているかを返す。
参考: https://vercel.com/blog/making-agent-friendly-pages-with-content-negotiation
(CDNやプロキシがAcceptヘッダーを書き換える場合があるという話もあるが…。)
戻り値:
| タイプ | デスクリプション |
|---|---|
bool
|
markdownがHTMLより優先されている場合True、そうでなければFalse |
ソースコード位置: pytilpack/quart/misc.py
static_url_for(filename, cache_busting=True, cache_timestamp='when_not_debug', **kwargs)
¶
静的ファイルのURLを生成する。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
filename
|
str
|
静的ファイルの名前 |
必須 |
cache_busting
|
bool
|
キャッシュバスティングを有効にするかどうか (デフォルト: True) |
True
|
cache_timestamp
|
bool | Literal['when_not_debug']
|
キャッシュバスティングするときのファイルの最終更新日時をプロセス単位でキャッシュするか否か。 - True: プロセス単位でキャッシュする。プロセスの再起動やSIGHUPなどをしない限り更新されない。 - False: キャッシュしない。常に最新を参照する。 - "when_not_debug": デバッグモードでないときのみキャッシュする。 |
'when_not_debug'
|
**kwargs
|
Any
|
その他の引数 (quart.url_forに渡される) |
{}
|
戻り値:
| タイプ | デスクリプション |
|---|---|
str
|
静的ファイルのURL |
ソースコード位置: pytilpack/quart/misc.py
get_routes(app)
¶
ルーティング情報を取得する。
戻り値:
| タイプ | デスクリプション |
|---|---|
list[RouteInfo]
|
ルーティング情報のリスト。 |
run(app, host='localhost', port=5000)
async
¶
Quartアプリを実行するコンテキストマネージャ。テストコードなど用。
ソースコード位置: pytilpack/quart/misc.py
asserts
¶
Quartのテストコード用アサーション関数。
ResponseType = quart.Response | typing.Awaitable[quart.Response]
module-attribute
¶
レスポンスの型。
assert_bytes(response, status_code=200, content_type=None)
async
¶
レスポンスのステータスコードとContent-Typeを検証してボディをbytesで返す。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
status_code
|
int
|
期待するステータスコード |
200
|
content_type
|
str | Iterable[str] | None
|
期待するContent-Type |
None
|
発生:
| タイプ | デスクリプション |
|---|---|
AssertionError
|
ステータスコードが異なる場合など |
戻り値:
| タイプ | デスクリプション |
|---|---|
bytes
|
レスポンスボディ |
ソースコード位置: pytilpack/quart/asserts.py
assert_html(response, status_code=200, content_type='__default__', strict=False, tmp_path=None)
async
¶
レスポンスを検証してHTMLボディを文字列で返す。html5libが必要。
strict・tmp_pathはキーワード引数での指定を推奨する。flask/quart/fastapi間で 位置引数順を揃えているが、将来の引数追加時の後方互換性を保つためである。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
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/quart/asserts.py
assert_json(response, status_code=200, content_type='application/json')
async
¶
レスポンスを検証してJSONをデコードして返す。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
status_code
|
int
|
期待するステータスコード |
200
|
content_type
|
str | Iterable[str] | None
|
期待するContent-Type |
'application/json'
|
発生:
| タイプ | デスクリプション |
|---|---|
AssertionError
|
ステータスコードが異なる場合など |
戻り値:
| タイプ | デスクリプション |
|---|---|
Any
|
レスポンスのJSONデコード結果 |
ソースコード位置: pytilpack/quart/asserts.py
assert_xml(response, status_code=200, content_type='__default__')
async
¶
レスポンスを検証してXMLボディを文字列で返す。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
status_code
|
int
|
期待するステータスコード |
200
|
content_type
|
str | Iterable[str] | None
|
期待するContent-Type |
'__default__'
|
発生:
| タイプ | デスクリプション |
|---|---|
AssertionError
|
ステータスコードが異なる場合など |
戻り値:
| タイプ | デスクリプション |
|---|---|
str
|
レスポンスボディ |
ソースコード位置: pytilpack/quart/asserts.py
assert_sse(response, status_code=200)
async
¶
レスポンスのステータスコードとSSE用Content-Typeを検証して返す。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
status_code
|
int
|
期待するステータスコード |
200
|
発生:
| タイプ | デスクリプション |
|---|---|
AssertionError
|
ステータスコードが異なる場合、またはContent-Typeが異なる場合 |
戻り値:
| タイプ | デスクリプション |
|---|---|
Response
|
レスポンス |
ソースコード位置: pytilpack/quart/asserts.py
assert_response(response, status_code=200)
async
¶
レスポンスのステータスコードを検証して返す。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
response
|
ResponseType
|
レスポンス |
必須 |
status_code
|
int
|
期待するステータスコード |
200
|
発生:
| タイプ | デスクリプション |
|---|---|
AssertionError
|
ステータスコードが異なる場合 |
戻り値:
| タイプ | デスクリプション |
|---|---|
Response
|
レスポンス |
ソースコード位置: pytilpack/quart/asserts.py
i18n
¶
Quart用i18n統合。
init_app(app, locale_dir, domain='messages', supported_locales=None, default_locale='en', fallback=True)
¶
Quartアプリにi18nを統合する。
before_requestでAccept-Languageからロケールを自動設定し、 Jinja2テンプレートに翻訳関数を登録する。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
app
|
Quart
|
Quartアプリ |
必須 |
locale_dir
|
str | Path
|
localeディレクトリのパス |
必須 |
domain
|
str
|
gettextドメイン名 |
'messages'
|
supported_locales
|
list[str] | None
|
サポートするロケール一覧 |
None
|
default_locale
|
str
|
デフォルトロケール |
'en'
|
fallback
|
bool
|
フォールバック有無 |
True
|
ソースコード位置: pytilpack/quart/i18n.py
misc
¶
Quart関連のその他のユーティリティ。
ConcurrencyState(semaphore, max_concurrency, timeout)
dataclass
¶
set_max_concurrency の内部状態。exhaust_concurrency から参照される。
set_max_concurrency(app, max_concurrency, timeout=3.0)
¶
Quart アプリ全体の最大同時リクエスト数を制限する。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
app
|
Quart
|
対象の Quart アプリケーション。 |
必須 |
max_concurrency
|
int
|
許可する同時リクエスト数の上限。 |
必須 |
timeout
|
float | None
|
最大待機秒数。タイムアウト時は 503 Service Unavailable を返す。 |
3.0
|
発生:
| タイプ | デスクリプション |
|---|---|
ValueError
|
max_concurrency が 1 未満の場合。 |
RuntimeError
|
同一アプリに対して二度目に呼び出した場合。 before_request/teardown_request が重複登録されてセマフォが二重管理されるため、 同一アプリへの複数回呼び出しは禁止する。 |
Notes
- Hypercorn の
--workers/--threadsとは独立した アプリレベルの制御。1 ワーカー内のコルーチン数を制限する用途で使う。
ソースコード位置: pytilpack/quart/misc.py
exhaust_concurrency(app)
async
¶
テスト用: セマフォを枯渇させて503を発生させるコンテキストマネージャ。
使用例::
async with pytilpack.quart.exhaust_concurrency(app):
response = await client.get("/any-endpoint")
assert response.status_code == 503
ソースコード位置: pytilpack/quart/misc.py
run_sync(func)
¶
同期関数を非同期に実行するデコレーター。
quart.utils.run_syncの型ヒントがいまいちなので用意。
ソースコード位置: pytilpack/quart/misc.py
get_next_url()
¶
prefer_markdown()
¶
AcceptヘッダーでmarkdownがHTMLより優先されているかを返す。
参考: https://vercel.com/blog/making-agent-friendly-pages-with-content-negotiation
(CDNやプロキシがAcceptヘッダーを書き換える場合があるという話もあるが…。)
戻り値:
| タイプ | デスクリプション |
|---|---|
bool
|
markdownがHTMLより優先されている場合True、そうでなければFalse |
ソースコード位置: pytilpack/quart/misc.py
static_url_for(filename, cache_busting=True, cache_timestamp='when_not_debug', **kwargs)
¶
静的ファイルのURLを生成する。
引数:
| 名前 | タイプ | デスクリプション | デフォルト |
|---|---|---|---|
filename
|
str
|
静的ファイルの名前 |
必須 |
cache_busting
|
bool
|
キャッシュバスティングを有効にするかどうか (デフォルト: True) |
True
|
cache_timestamp
|
bool | Literal['when_not_debug']
|
キャッシュバスティングするときのファイルの最終更新日時をプロセス単位でキャッシュするか否か。 - True: プロセス単位でキャッシュする。プロセスの再起動やSIGHUPなどをしない限り更新されない。 - False: キャッシュしない。常に最新を参照する。 - "when_not_debug": デバッグモードでないときのみキャッシュする。 |
'when_not_debug'
|
**kwargs
|
Any
|
その他の引数 (quart.url_forに渡される) |
{}
|
戻り値:
| タイプ | デスクリプション |
|---|---|
str
|
静的ファイルのURL |
ソースコード位置: pytilpack/quart/misc.py
get_routes(app)
¶
ルーティング情報を取得する。
戻り値:
| タイプ | デスクリプション |
|---|---|
list[RouteInfo]
|
ルーティング情報のリスト。 |
run(app, host='localhost', port=5000)
async
¶
Quartアプリを実行するコンテキストマネージャ。テストコードなど用。
ソースコード位置: pytilpack/quart/misc.py
proxy_fix
¶
リバースプロキシ対応。
ProxyFix(quartapp, x_for=1, x_proto=1, x_host=0, x_port=0, x_prefix=1, static_prefix=None, trusted_hosts=None)
¶
リバースプロキシ対応。
nginx.conf設定例::
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Prefix $http_x_forwarded_prefix;
参考
- hypercorn.middleware.ProxyFixMiddleware https://github.com/pgjones/hypercorn/blob/main/src/hypercorn/middleware/proxy_fix.py
設計意図
prefixはpin方式で管理する。初回有効リクエスト時に一度だけapp.configへ反映し、
以降は変更しない。これにより攻撃者制御のX-Forwarded-Prefix値で
Cookie発行パス等のアプリ全体設定が書き換わる経路を断つ。
運用時にprefixが既知であれば、static_prefix引数で起動時に確定させることを推奨する。
static_prefixを指定した場合、初期化時にapp.configを確定する。
ただしscope["root_path"]はリクエストごとにX-Forwarded-Prefixヘッダーの値から
設定するため、ヘッダーが来ないリクエストではscope["root_path"]は更新されない。
x_host > 0でX-Forwarded-Hostを有効化する場合は、trusted_hosts引数で
許可ホストリストを指定することを推奨する。指定がない場合は値検証(制御文字・
//始まり等の排除)のみ行い追加警告は出力しない。
旧挙動(リクエストごとにapp.configを上書き)を期待していた利用者は、
static_prefix指定方式、またはpin後の挙動(初回リクエストでpinされる)へ移行すること。
ソースコード位置: pytilpack/quart/proxy_fix.py
__call__(scope, receive, send)
async
¶
ASGIアプリケーションとしての処理。
ソースコード位置: pytilpack/quart/proxy_fix.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | |