add Quart utilities add_i18n(), negotiate() add_rest()

This commit is contained in:
Yusur 2025-08-10 09:09:20 +02:00
parent 5bdf13b104
commit a23cad2e45
4 changed files with 95 additions and 2 deletions

View file

@ -11,6 +11,7 @@
+ Add `redact` module with `redact_url_password()`
+ Add more exceptions: `NotFoundError()`, `BabelTowerError()`
+ Add `sass` module
+ Add `quart` module with `negotiate()`, `add_rest()`, `add_i18n()`, `WantsContentType`
## 0.4.0

View file

@ -67,10 +67,11 @@ def get_flask_conf(key: str, default = None, *, app: Flask | None = None) -> Any
app = current_app
return app.config.get(key, default)
## XXX UNTESTED!
def harden(app: Flask):
"""
Make common "dork" endpoints unavailable
XXX UNTESTED!
"""
i = 1
for ep in SENSITIVE_ENDPOINTS:
@ -81,6 +82,7 @@ def harden(app: Flask):
return app
# Optional dependency: do not import into __init__.py
__all__ = ('add_context_from_config', 'add_i18n', 'get_flask_conf', 'harden')

24
src/suou/http.py Normal file
View file

@ -0,0 +1,24 @@
"""
Framework-agnostic utilities for web app development.
---
Copyright (c) 2025 Sakuragasaki46.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
See LICENSE for the specific language governing permissions and
limitations under the License.
This software is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
from __future__ import annotations
import enum
class WantsContentType(enum.Enum):
PLAIN = 'text/plain'
JSON = 'application/json'
HTML = 'text/html'

View file

@ -14,4 +14,70 @@ This software is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# TODO everything
from __future__ import annotations
from flask import current_app
from quart import Quart, request, g
from quart_schema import QuartSchema
from suou.http import WantsContentType
from .i18n import I18n
from .itertools import makelist
def add_i18n(app: Quart, i18n: I18n, var_name: str = 'T', *,
query_arg: str = 'lang', default_lang = 'en'):
'''
Integrate a I18n() object with a Quart application:
- set g.lang
- add T() to Jinja templates
XXX UNTESTED
'''
def _get_lang():
lang = request.args.get(query_arg)
if not lang:
for lp in request.headers.get('accept-language', 'en').split(','):
l = lp.split(';')[0]
lang = l
break
else:
lang = default_lang
return lang
@app.context_processor
def _add_i18n():
return {var_name: i18n.lang(_get_lang()).t}
@app.before_request
def _add_language_code():
g.lang = _get_lang()
return app
def negotiate() -> WantsContentType:
"""
Return an appropriate MIME type for content negotiation.
"""
if 'application/json' in request.accept_mimetypes or any(request.path.startswith(f'/{p.strip('/')}/') for p in current_app.config.get('REST_PATHS')):
return WantsContentType.JSON
elif request.user_agent.string.startswith('Mozilla/'):
return WantsContentType.HTML
else:
return WantsContentType.PLAIN
def add_rest(app: Quart, *bases: str, **kwargs) -> QuartSchema:
"""
Construct a REST ...
The rest of ...
"""
schema = QuartSchema(app, **kwargs)
app.config['REST_PATHS'] = makelist(bases, wrap=False)
return schema
__all__ = ('add_i18n', 'negotiate', 'add_rest')