Skip to content
Snippets Groups Projects
Commit 533884ec authored by Jakob Moser's avatar Jakob Moser
Browse files

Merge branch 'make-mypy-happy' into 'master'

Make MyPy happy

See merge request !22
parents 3807efc6 e1c153f0
No related branches found
No related tags found
1 merge request!22Make MyPy happy
Pipeline #6673 passed
......@@ -7,7 +7,7 @@ from ..model import Key
@bp.get("/agendas/-/items")
@require_valid(Key)
def the_agenda_items():
def the_agenda_items() -> list[dict[str, str]]:
tasks = sorted(
TaigaUserStoryProvider.get_the_one().fetch_user_stories(),
key=lambda task: task["kanban_order"],
......
# Login code adapted from the flaskr tutorial (see LICENSE-3RD-PARTY.md for details)
# See https://flask.palletsprojects.com/en/2.0.x/tutorial/views/
import functools
from typing import Any, Callable
from flask import request, g
from flask import g, request
from portal import db
from ..model import Key, User
from ..problem_details import ProblemResponse, not_found, unauthorized
from .blueprint import bp
from ..model import User, Key
from ..problem_details import not_found, unauthorized
@bp.before_app_request
def set_user_if_valid():
if not request.authorization or request.authorization.type != "basic":
def set_user_if_valid() -> None:
if (
not request.authorization
or request.authorization.type != "basic"
or request.authorization.username is None
or request.authorization.password is None
):
g.user = None
return
......@@ -23,15 +29,19 @@ def set_user_if_valid():
@bp.before_app_request
def set_key_if_valid():
if not request.authorization or request.authorization.type != "bearer":
def set_key_if_valid() -> None:
if (
not request.authorization
or request.authorization.type != "bearer"
or request.authorization.token is None
):
g.key = None
return
g.key = Key.get_by_secret_unless_expired(request.authorization.token)
def require_valid(credential_type):
def require_valid(credential_type: type[User | Key]) -> Callable:
"""
Requires that valid credentials are provided before allowing access to a route.
If no valid credentials are provided, abort and send a 401.
......@@ -41,9 +51,9 @@ def require_valid(credential_type):
- `@require_valid(Key)` to require a valid key
"""
def decorator(route):
def decorator(route: Callable) -> Callable:
@functools.wraps(route)
def protected_route(**kwargs):
def protected_route(**kwargs: Any) -> Any:
# Determine which credentials to use
credentials = {User: g.user, Key: g.key}.get(credential_type)
......@@ -60,14 +70,14 @@ def require_valid(credential_type):
@bp.post("/keys")
@require_valid(User)
def post_key():
def post_key() -> tuple[dict, int]:
key, secret = Key.generate(g.user)
return key.to_dict() | {"secret": secret}, 201
@bp.delete("/keys/<string:uuid>")
@require_valid(Key)
def delete_key(uuid: str):
def delete_key(uuid: str) -> tuple[str, int] | ProblemResponse:
key_to_delete = Key.get_only(uuid)
if not key_to_delete:
......
from flask import render_template
from ..problem_details import ProblemResponse, not_found
from .blueprint import bp
from ..problem_details import not_found
@bp.get("/<path:_>")
def fallback_not_found(_):
def fallback_not_found(_: str) -> ProblemResponse:
"""
If you can't find a matching API endpoint, fallback to returning a 404.
......@@ -16,5 +16,5 @@ def fallback_not_found(_):
@bp.get("/")
def index():
def index() -> str:
return render_template("redocly.html")
import json
from datetime import datetime
from pathlib import Path
from typing import Any
from .blueprint import bp
@bp.get("/version")
def get_version():
def get_version() -> dict[str, Any]:
portal_version_path = Path("./PORTAL_VERSION")
if portal_version_path.is_file():
with open("PORTAL_VERSION") as f:
......
from flask import Blueprint, render_template, redirect, url_for
from flask import Blueprint, redirect, render_template, url_for
from werkzeug import Response
bp = Blueprint("main", __name__)
@bp.get("/", defaults={"_": ""})
@bp.get("/<path:_>")
def index(_):
def index(_: str) -> str:
"""
Serve the index page at /*, i.e. at any path below / and at / itself.
......@@ -16,5 +17,5 @@ def index(_):
@bp.get("/api/")
def redirect_to_api():
def redirect_to_api() -> Response:
return redirect(url_for("api.index"))
from abc import abstractmethod
from typing import List, Optional, Any, Self
from typing import Any, List, Optional, Self
from flask import g
from portal import db
......
......@@ -28,15 +28,15 @@ class UtcDateTime(types.TypeDecorator):
return types.String(32)
def process_bind_param(
self, value: datetime, dialect: Dialect
) -> Optional[str | datetime]:
self, value: Optional[datetime], dialect: Dialect
) -> Optional[str]:
if value is None:
return None
return value.astimezone(timezone.utc).isoformat()
def process_result_value(
self, value: str | datetime, dialect: Dialect
self, value: Optional[str], dialect: Dialect
) -> Optional[datetime]:
if value is None:
return None
......
......@@ -26,7 +26,9 @@ class Uuid(types.TypeDecorator):
def load_dialect_impl(self, dialect: Dialect) -> types.TypeEngine[Any]:
return types.String(UUID_LENGTH)
def process_bind_param(self, value: str | UUID, dialect: Dialect) -> Optional[str]:
def process_bind_param(
self, value: Optional[str | UUID], dialect: Dialect
) -> Optional[str]:
if isinstance(value, str):
# Manually create UUID from string, to raise an error if the string is malformed
UUID(value)
......
......@@ -2,17 +2,22 @@
Provide RFC 7807-compliant problem details responses for the API.
"""
from typing import Optional
from typing import Any, Optional
ResponseBody = dict[str, Any]
HttpStatusCode = int
HttpHeaders = dict[str, str]
ProblemResponse = tuple[ResponseBody, HttpStatusCode, HttpHeaders]
def unauthorized():
def unauthorized() -> ProblemResponse:
"""
Return a 401 message and status code
"""
return _problem_response(title="Unauthorized", status=401)
def not_found():
def not_found() -> ProblemResponse:
"""
Return a 404 message and status code
"""
......@@ -24,7 +29,7 @@ def _problem_response(
status: int,
type_uri: Optional[str] = None,
extensions: Optional[dict] = None,
) -> tuple[dict, int, dict]:
) -> ProblemResponse:
type_dict = {"type": type_uri} if type_uri is not None else {}
title_dict = {"title": title}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment