diff --git a/app/forms/__init__.py b/app/forms/__init__.py new file mode 100644 index 0000000..9cf5836 --- /dev/null +++ b/app/forms/__init__.py @@ -0,0 +1,27 @@ +"""Module containing WTForms classes +and helper functions for Starlette-WTF""" + +from typing import Type, TypeVar + +from fastapi import Request +from starlette_wtf import StarletteForm + +T = TypeVar('T', bound=StarletteForm) + + +async def get_form( + form: Type[T], + req: Request) -> T: + """Almost the same as `form.from_formdata`, + and must be used *instead* of instantiatng + form object directly as in Flask + + Args: + form (Type[StarletteForm]): StarletteForm class + req (Request): Request object + + Returns: + StarletteForm instance + """ + + return await form.from_formdata(request=req) diff --git a/app/main.py b/app/main.py index 60f469d..65764ba 100644 --- a/app/main.py +++ b/app/main.py @@ -9,8 +9,8 @@ from starlette_wtf import CSRFProtectMiddleware from . import common from .sql import db -# Add your paths here -from .paths.paths import Paths +from .paths import Paths +# Add your paths below from .paths import pages from .paths import table from .paths import errors diff --git a/app/paths/paths.py b/app/paths/__init__.py similarity index 91% rename from app/paths/paths.py rename to app/paths/__init__.py index 82670ae..16d789d 100644 --- a/app/paths/paths.py +++ b/app/paths/__init__.py @@ -1,3 +1,5 @@ +"""Module containing FastAPI paths""" + import abc from fastapi import FastAPI diff --git a/app/paths/errors.py b/app/paths/errors.py index 0a0cbd3..d2de3c8 100644 --- a/app/paths/errors.py +++ b/app/paths/errors.py @@ -5,7 +5,7 @@ from pathlib import Path from fastapi import Request, Response from fastapi import HTTPException -from . import paths +from . import Paths from .. import respond from .. import common @@ -13,7 +13,7 @@ from .. import common codes = [404, 500] -class ErrorsPaths(paths.Paths): +class ErrorsPaths(Paths): """Sets up custom error pages, inherited from paths.Paths""" diff --git a/app/paths/pages.py b/app/paths/pages.py index c2e784c..8cd9a95 100644 --- a/app/paths/pages.py +++ b/app/paths/pages.py @@ -2,11 +2,11 @@ from fastapi import Request, Response -from . import paths +from . import Paths from .. import respond -class MainPaths(paths.Paths): +class MainPaths(Paths): """Main FastAPI app paths, inherits paths.Paths""" diff --git a/app/paths/table.py b/app/paths/table.py index 06d507b..f1c7481 100644 --- a/app/paths/table.py +++ b/app/paths/table.py @@ -1,3 +1,5 @@ +"""Paths related to working with database""" + from sqlalchemy.orm import Session from fastapi import Depends @@ -5,17 +7,18 @@ from fastapi import Request, Response from starlette_wtf import csrf_protect -from . import paths +from . import Paths from .. import respond from ..sql import db from ..sql import crud from ..sql import schemas +from ..forms import get_form from ..forms.users import AddUserForm LIMIT = 10 -class TablePaths(paths.Paths): +class TablePaths(Paths): def add_paths(self) -> None: @@ -42,7 +45,7 @@ class TablePaths(paths.Paths): req: Request, db_s: Session = Depends(db.get_db)) -> Response: - form = await AddUserForm.from_formdata(request=req) + form = await get_form(AddUserForm, req) if await form.validate_on_submit(): diff --git a/app/respond.py b/app/respond.py index 1624f3b..027d358 100644 --- a/app/respond.py +++ b/app/respond.py @@ -1,8 +1,9 @@ import os import mimetypes +from functools import partial from typing import Optional, Mapping -from fastapi import Response +from fastapi import Request, Response from fastapi.responses import RedirectResponse from fastapi.responses import PlainTextResponse from fastapi.responses import FileResponse @@ -15,19 +16,17 @@ from .common import templates def with_redirect( url: str = '/', code: int = 302, - headers: Optional[Mapping[str, str]] = None, - background: Optional[BackgroundTask] = None) -> Response: - """Return a redirect to the page specified in `url` + *args, **kwargs) -> RedirectResponse: + """Return a redirect to the page specified in `url`. + By default, code is 302 so method is changed to GET. + To leave the same HTTP method, use 307 status code + or call `with_redirect_307` function. + `args` and `kwargs` are passed directly + to the Response contructor Args: - url (str, optional): - Target URL (Location header), - root by default + url (str, optional): Target URL, root by default code (int, optional): HTTP response code - headers (Optional[Mapping[str, str]], optional): - Additional headers, passed to Response constructor - background (Optional[BackgroundTask], optional): - Background task, passed to Response constructor Returns: FastAPI's RedirectResponse object @@ -36,25 +35,21 @@ def with_redirect( return RedirectResponse( url=url, status_code=code, - headers=headers, - background=background, + *args, **kwargs, ) def with_text( content: str, code: int = 200, - headers: Optional[Mapping[str, str]] = None, - background: Optional[BackgroundTask] = None) -> Response: - """Return a plain text to the user + *args, **kwargs) -> PlainTextResponse: + """Return a plain text to the user. + `args` and `kwargs` are passed directly + to the Response contructor Args: content (str): Plain text content code (int, optional): HTTP response code - headers (Optional[Mapping[str, str]], optional): - Additional headers, passed to Response constructor - background (Optional[BackgroundTask], optional): - Background task, passed to Response constructor Returns: FastAPI's PlainTextResponse object @@ -63,13 +58,13 @@ def with_text( return PlainTextResponse( content=content, status_code=code, - headers=headers, - background=background, + *args, **kwargs, ) def with_tmpl( name: str, + request: Request, code: int = 200, headers: Optional[Mapping[str, str]] = None, background: Optional[BackgroundTask] = None, @@ -79,6 +74,7 @@ def with_tmpl( Args: name (str): Template filename + request (Request): FastAPI request object code (int, optional): HTTP response code headers (Optional[Mapping[str, str]], optional): Additional headers, passed to Response constructor @@ -91,7 +87,10 @@ def with_tmpl( return templates.TemplateResponse( name=name, - context=context, + context={ + 'request': request, + **context, + }, status_code=code, headers=headers, background=background, @@ -102,19 +101,16 @@ def with_file( path: os.PathLike, mime: Optional[str] = None, code: int = 200, - headers: Optional[Mapping[str, str]] = None, - background: Optional[BackgroundTask] = None) -> FileResponse: + *args, **kwargs) -> FileResponse: """Send a file specified in `path` - automatically guessing mimetype if `mime` is None + automatically guessing mimetype if `mime` is None. + `args` and `kwargs` are passed directly + to the Response contructor Args: path (os.PathLike): File path mime (Optional[str], optional): File mimetype code (int, optional): HTTP response code - headers (Optional[Mapping[str, str]], optional): - Additional headers, passed to Response constructor - background (Optional[BackgroundTask], optional): - Background task, passed to Response constructor Returns: FileResponse: FastAPI's FileResponse object @@ -127,10 +123,15 @@ def with_file( mimetypes.guess_type(path)[0] ), status_code=code, - headers=headers, - background=background, + *args, **kwargs, ) -# Alias +# Aliases with_template = with_tmpl +with_redirect_302 = partial( + with_redirect, code=302, +) +with_redirect_307 = partial( + with_redirect, code=307, +)