diff --git a/requirements.txt b/requirements.txt index f4c81ec..6be62e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ aiohttp~=3.6 -quart~=0.11,<0.15 +quart~=0.17 flask-wtf~=0.14 hsluv~=0.0.2 flask-babel~=1.0 diff --git a/snikket_web/__init__.py b/snikket_web/__init__.py index 5ba3b78..b352cc1 100644 --- a/snikket_web/__init__.py +++ b/snikket_web/__init__.py @@ -18,6 +18,8 @@ from quart import ( jsonify, ) +import werkzeug.exceptions + import environ from . import colour, infra @@ -40,7 +42,7 @@ async def proc() -> typing.Dict[str, typing.Any]: try: user_info = await infra.client.get_user_info() - except (aiohttp.ClientError, quart.exceptions.HTTPException): + except (aiohttp.ClientError, werkzeug.exceptions.HTTPException): user_info = {} return { @@ -105,16 +107,16 @@ async def backend_error_handler(exc: Exception) -> quart.Response: async def generic_http_error( - exc: quart.exceptions.HTTPException, + exc: werkzeug.exceptions.HTTPException, ) -> quart.Response: return quart.Response( await render_template( "generic_http_error.html", - status=exc.status_code, + status=exc.code, description=exc.description, name=exc.name, ), - status=exc.status_code, + status=exc.code, ) @@ -200,19 +202,19 @@ def create_app() -> quart.Quart: app.context_processor(proc) app.register_error_handler( aiohttp.ClientConnectorError, - backend_error_handler, # type:ignore + backend_error_handler, ) app.register_error_handler( - quart.exceptions.HTTPException, + werkzeug.exceptions.HTTPException, generic_http_error, # type:ignore ) app.register_error_handler( Exception, - generic_error_handler, # type:ignore + generic_error_handler, ) @app.route("/") - async def index() -> quart.Response: + async def index() -> werkzeug.Response: if infra.client.has_session: return redirect(url_for('user.index')) diff --git a/snikket_web/admin.py b/snikket_web/admin.py index 94c9b81..afcb9b7 100644 --- a/snikket_web/admin.py +++ b/snikket_web/admin.py @@ -7,6 +7,8 @@ from datetime import datetime import aiohttp +import werkzeug.exceptions + import quart.flask_patch import wtforms @@ -92,7 +94,7 @@ class EditUserForm(BaseForm): @bp.route("/user//", methods=["GET", "POST"]) @client.require_admin_session() -async def edit_user(localpart: str) -> typing.Union[quart.Response, str]: +async def edit_user(localpart: str) -> typing.Union[werkzeug.Response, str]: target_user_info = await client.get_user_by_localpart(localpart) form = EditUserForm() @@ -147,7 +149,7 @@ class DeleteUserForm(BaseForm): @bp.route("/user//delete", methods=["GET", "POST"]) @client.require_admin_session() -async def delete_user(localpart: str) -> typing.Union[str, quart.Response]: +async def delete_user(localpart: str) -> typing.Union[str, werkzeug.Response]: target_user_info = await client.get_user_by_localpart(localpart) form = DeleteUserForm() if form.validate_on_submit(): @@ -186,7 +188,7 @@ async def debug_user(localpart: str) -> typing.Union[str, quart.Response]: @client.require_admin_session() async def user_password_reset_link( id_: str, - ) -> typing.Union[str, quart.Response]: + ) -> typing.Union[str, werkzeug.Response]: invite_info = await client.get_invite_by_id( id_, ) @@ -278,7 +280,7 @@ class InvitePost(BaseForm): @bp.route("/invitations", methods=["GET", "POST"]) @client.require_admin_session() -async def invitations() -> typing.Union[str, quart.Response]: +async def invitations() -> typing.Union[str, werkzeug.Response]: invites = sorted( ( invite @@ -324,7 +326,7 @@ class InviteForm(BaseForm): @bp.route("/invitation/-/new", methods=["POST"]) @client.require_admin_session() -async def create_invite() -> typing.Union[str, quart.Response]: +async def create_invite() -> typing.Union[str, werkzeug.Response]: form = InvitePost() circles = await client.list_groups() form.circles.choices = [ @@ -352,7 +354,7 @@ async def create_invite() -> typing.Union[str, quart.Response]: @bp.route("/invitation/", methods=["GET", "POST"]) @client.require_admin_session() -async def edit_invite(id_: str) -> typing.Union[str, quart.Response]: +async def edit_invite(id_: str) -> typing.Union[str, werkzeug.Response]: try: invite_info = await client.get_invite_by_id(id_) except aiohttp.ClientResponseError as exc: @@ -418,7 +420,7 @@ async def circles() -> str: @bp.route("/circle/-/new", methods=["POST"]) @client.require_admin_session() -async def create_circle() -> typing.Union[str, quart.Response]: +async def create_circle() -> typing.Union[str, werkzeug.Response]: create_form = CirclePost() if create_form.validate_on_submit(): circle = await client.create_group( @@ -464,7 +466,7 @@ class EditCircleForm(BaseForm): @bp.route("/circle/", methods=["GET", "POST"]) @client.require_admin_session() -async def edit_circle(id_: str) -> typing.Union[str, quart.Response]: +async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]: async with client.authenticated_session() as session: try: circle = await client.get_group_by_id( @@ -626,7 +628,7 @@ class AnnouncementForm(BaseForm): @bp.route("/system/", methods=["GET", "POST"]) @client.require_admin_session() -async def system() -> typing.Union[str, quart.Response]: +async def system() -> typing.Union[str, werkzeug.Response]: form = AnnouncementForm() if form.validate_on_submit(): @@ -657,7 +659,7 @@ async def system() -> typing.Union[str, quart.Response]: now = time.time() try: prosody_metrics = await client.get_system_metrics() - except quart.exceptions.NotFound: + except werkzeug.exceptions.NotFound: # server does not offer the endpoint for whatever reason -- ignore prosody_metrics = {} diff --git a/snikket_web/invite.py b/snikket_web/invite.py index dcf562c..4275c8a 100644 --- a/snikket_web/invite.py +++ b/snikket_web/invite.py @@ -15,6 +15,8 @@ from quart import ( session as http_session, ) +import werkzeug + import wtforms from flask_babel import lazy_gettext as _l, gettext @@ -46,14 +48,14 @@ def apple_store_badge() -> str: @bp.context_processor -def context() -> typing.Mapping[str, typing.Any]: +def context() -> typing.Dict[str, typing.Any]: return { "apple_store_badge": apple_store_badge, } @bp.route("/") -async def view_old(id_: str) -> quart.Response: +async def view_old(id_: str) -> werkzeug.Response: return redirect(url_for(".view", id_=id_)) @@ -131,7 +133,7 @@ class RegisterForm(BaseForm): @bp.route("//register", methods=["GET", "POST"]) -async def register(id_: str) -> typing.Union[str, quart.Response]: +async def register(id_: str) -> typing.Union[str, werkzeug.Response]: try: invite = await client.get_public_invite_by_id(id_) except aiohttp.ClientResponseError as exc: @@ -199,7 +201,7 @@ class ResetForm(BaseForm): @bp.route("//reset", methods=["GET", "POST"]) -async def reset(id_: str) -> typing.Union[str, quart.Response]: +async def reset(id_: str) -> typing.Union[str, werkzeug.Response]: try: invite = await client.get_public_invite_by_id(id_) except aiohttp.ClientResponseError as exc: @@ -300,5 +302,5 @@ async def reset_success() -> str: @bp.route("/-") -async def index() -> quart.Response: +async def index() -> werkzeug.Response: return redirect(url_for("index")) diff --git a/snikket_web/main.py b/snikket_web/main.py index a3db81d..73b3ff2 100644 --- a/snikket_web/main.py +++ b/snikket_web/main.py @@ -18,6 +18,8 @@ from quart import ( flash, ) +import werkzeug.exceptions + import babel import wtforms @@ -48,7 +50,7 @@ class LoginForm(BaseForm): @bp.route("/-") -async def index() -> quart.Response: +async def index() -> werkzeug.Response: return redirect(url_for("index")) @@ -56,7 +58,7 @@ ERR_CREDENTIALS_INVALID = _l("Invalid username or password.") @bp.route("/login", methods=["GET", "POST"]) -async def login() -> typing.Union[str, quart.Response]: +async def login() -> typing.Union[str, werkzeug.Response]: if client.has_session and (await client.test_session()): return redirect(url_for('user.index')) @@ -76,7 +78,7 @@ async def login() -> typing.Union[str, quart.Response]: password = form.password.data try: await client.login(jid, password) - except quart.exceptions.Unauthorized: + except werkzeug.exceptions.Unauthorized: form.password.errors.append(ERR_CREDENTIALS_INVALID) else: await flash( @@ -95,14 +97,13 @@ async def about() -> str: if current_app.debug or client.is_admin_session: version = _version.version - extra_versions["Quart"] = quart.__version__ extra_versions["aiohttp"] = aiohttp.__version__ extra_versions["babel"] = babel.__version__ extra_versions["wtforms"] = wtforms.__version__ extra_versions["flask-wtf"] = flask_wtf.__version__ try: extra_versions["Prosody"] = await client.get_server_version() - except quart.exceptions.Unauthorized: + except werkzeug.exceptions.Unauthorized: extra_versions["Prosody"] = "unknown" return await render_template( diff --git a/snikket_web/prosodyclient.py b/snikket_web/prosodyclient.py index f065c25..6d6f0ac 100644 --- a/snikket_web/prosodyclient.py +++ b/snikket_web/prosodyclient.py @@ -19,7 +19,9 @@ from quart import ( current_app, _app_ctx_stack, session as http_session, abort, redirect, url_for, ) -import quart.exceptions +import quart + +import werkzeug.exceptions from . import xmpputil from .xmpputil import split_jid @@ -386,16 +388,16 @@ class ProsodyClient: ) -> typing.Callable[ [typing.Callable[..., typing.Awaitable[T]]], typing.Callable[..., typing.Awaitable[ - typing.Union[T, quart.Response]]]]: + typing.Union[T, quart.Response, werkzeug.Response]]]]: def decorator( f: typing.Callable[..., typing.Awaitable[T]], ) -> typing.Callable[..., typing.Awaitable[ - typing.Union[T, quart.Response]]]: + typing.Union[T, quart.Response, werkzeug.Response]]]: @functools.wraps(f) async def wrapped( *args: typing.Any, **kwargs: typing.Any, - ) -> typing.Union[T, quart.Response]: + ) -> typing.Union[T, quart.Response, werkzeug.Response]: if not self.has_session or not (await self.test_session()): redirect_to_value = redirect_to if redirect_to_value is not False: @@ -415,17 +417,17 @@ class ProsodyClient: ) -> typing.Callable[ [typing.Callable[..., typing.Awaitable[T]]], typing.Callable[..., typing.Awaitable[ - typing.Union[T, quart.Response]]]]: + typing.Union[T, quart.Response, werkzeug.Response]]]]: def decorator( f: typing.Callable[..., typing.Awaitable[T]], ) -> typing.Callable[..., typing.Awaitable[ - typing.Union[T, quart.Response]]]: + typing.Union[T, quart.Response, werkzeug.Response]]]: @functools.wraps(f) @self.require_session(redirect_to=redirect_to) async def wrapped( *args: typing.Any, **kwargs: typing.Any, - ) -> typing.Union[T, quart.Response]: + ) -> typing.Union[T, quart.Response, werkzeug.Response]: if not self.is_admin_session: raise abort(403, "This is not for you.") @@ -492,7 +494,7 @@ class ProsodyClient: session=session, ) avatar_hash = avatar_info["sha1"] - except quart.exceptions.HTTPException: + except werkzeug.exceptions.HTTPException: avatar_hash = None return { @@ -644,7 +646,7 @@ class ProsodyClient: new_access_model, ) )) - except quart.exceptions.NotFound: + except werkzeug.exceptions.NotFound: if ignore_not_found: return raise @@ -774,7 +776,7 @@ class ProsodyClient: session: aiohttp.ClientSession, ) -> str: access_models = filter( - lambda x: not isinstance(x, quart.exceptions.NotFound), + lambda x: not isinstance(x, werkzeug.exceptions.NotFound), await asyncio.gather( self.get_avatar_access_model(session=session), self.get_nickname_access_model(session=session), diff --git a/snikket_web/user.py b/snikket_web/user.py index 4b238fa..beb2539 100644 --- a/snikket_web/user.py +++ b/snikket_web/user.py @@ -13,7 +13,7 @@ from quart import ( flash, current_app, ) -import quart.exceptions +import werkzeug.exceptions import wtforms @@ -96,7 +96,7 @@ async def index() -> str: @bp.route('/passwd', methods=["GET", "POST"]) @client.require_session() -async def change_pw() -> typing.Union[str, quart.Response]: +async def change_pw() -> typing.Union[str, werkzeug.Response]: form = ChangePasswordForm() if form.validate_on_submit(): try: @@ -104,8 +104,8 @@ async def change_pw() -> typing.Union[str, quart.Response]: form.current_password.data, form.new_password.data, ) - except (quart.exceptions.Unauthorized, - quart.exceptions.Forbidden): + except (werkzeug.exceptions.Unauthorized, + werkzeug.exceptions.Forbidden): # server refused current password, set an appropriate error form.current_password.errors.append( _("Incorrect password."), @@ -128,7 +128,7 @@ EAVATARTOOBIG = _l( @bp.route("/profile", methods=["GET", "POST"]) @client.require_session() -async def profile() -> typing.Union[str, quart.Response]: +async def profile() -> typing.Union[str, werkzeug.Response]: max_avatar_size = current_app.config["MAX_AVATAR_SIZE"] form = ProfileForm() @@ -221,7 +221,7 @@ async def manage_data() -> typing.Union[str, quart.Response]: @bp.route("/logout", methods=["GET", "POST"]) @client.require_session() -async def logout() -> typing.Union[quart.Response, str]: +async def logout() -> typing.Union[werkzeug.Response, str]: form = LogoutForm() if form.validate_on_submit(): await client.logout() diff --git a/snikket_web/xmpputil.py b/snikket_web/xmpputil.py index cbbc2fe..f60956a 100644 --- a/snikket_web/xmpputil.py +++ b/snikket_web/xmpputil.py @@ -4,7 +4,7 @@ import typing import xml.etree.ElementTree as ET from quart import abort -import quart.exceptions +import werkzeug.exceptions TAG_XMPP_ERROR = "error" @@ -239,7 +239,7 @@ def extract_pubsub_item_get_reply( ) -> typing.Optional[ET.Element]: try: pubsub = extract_iq_reply(iq_tree, TAG_PUBSUB) - except quart.exceptions.NotFound: + except werkzeug.exceptions.NotFound: return None if pubsub is None: