From 221fe40a0440b86773ad17db967ed4335d9e4da9 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Tue, 12 May 2020 12:19:49 -0700 Subject: fastapi infrastructure - API/web distrinction - language code prefixes - content negotiation for endpoints --- fatcat_scholar/__init__.py | 0 fatcat_scholar/templates/home.html | 8 ++++ fatcat_scholar/templates/search.html | 1 + fatcat_scholar/web.py | 79 ++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 fatcat_scholar/__init__.py create mode 100644 fatcat_scholar/templates/home.html create mode 100644 fatcat_scholar/templates/search.html create mode 100644 fatcat_scholar/web.py (limited to 'fatcat_scholar') diff --git a/fatcat_scholar/__init__.py b/fatcat_scholar/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fatcat_scholar/templates/home.html b/fatcat_scholar/templates/home.html new file mode 100644 index 0000000..82eb69d --- /dev/null +++ b/fatcat_scholar/templates/home.html @@ -0,0 +1,8 @@ + + + hello + + +

The Start

+ + diff --git a/fatcat_scholar/templates/search.html b/fatcat_scholar/templates/search.html new file mode 100644 index 0000000..783cb47 --- /dev/null +++ b/fatcat_scholar/templates/search.html @@ -0,0 +1 @@ +

Search template will go here

diff --git a/fatcat_scholar/web.py b/fatcat_scholar/web.py new file mode 100644 index 0000000..dfebe01 --- /dev/null +++ b/fatcat_scholar/web.py @@ -0,0 +1,79 @@ + +""" +This file is the FastAPI web application. +""" + +from enum import Enum + +from fastapi import FastAPI, APIRouter, Request, Depends +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from fastapi.responses import HTMLResponse + +I18N_LANG_DEFAULT = "en" +I18N_LANG_OPTIONS = ["en", "de", "zh"] + +class LangPrefix: + """ + Looks for a two-character language prefix. + + If there is no such prefix, in the future it could also look at the + Accept-Language header and try to infer a language from that, while not + setting the prefix code. + """ + + def __init__(self, request: Request): + self.prefix : str = "" + self.code : str = I18N_LANG_DEFAULT + for lang_option in I18N_LANG_OPTIONS: + if request.url.path.startswith(f"/{lang_option}/"): + self.prefix = f"/{lang_option}" + self.code = lang_option + break + +class ContentNegotiation: + """ + Choses a mimetype to return based on Accept header. + + Intended to be used for RESTful content negotiation from web endpoints to API. + """ + + def __init__(self, request: Request): + self.mimetype = "text/html" + if request.headers.get('accept', '').startswith('application/json'): + self.mimetype = "application/json" + +api = APIRouter() + +@api.get("/", operation_id="get_home") +async def home(request: Request): + return {"endpoints": {"/": "this", "/search": "fulltext search"}} + +@api.get("/search", operation_id="get_search") +async def search(request: Request): + return {"message": "search results would go here, I guess"} + +web = APIRouter() +templates = Jinja2Templates(directory="fatcat_scholar/templates") + +@web.get("/", include_in_schema=False) +async def web_home(request: Request, lang: LangPrefix = Depends(LangPrefix), content: ContentNegotiation = Depends(ContentNegotiation)): + if content.mimetype == "application/json": + return await api_home(request) + return templates.TemplateResponse("home.html", {"request": request}) + +@web.get("/search", include_in_schema=False) +async def web_search(request: Request): + if content.mimetype == "application/json": + return await api_search(request) + return templates.TemplateResponse("search.html", {"request": request}) + +app = FastAPI() + + +app.include_router(web) +for lang_option in I18N_LANG_OPTIONS: + app.include_router(web, prefix=f"/{lang_option}") +app.include_router(api) +app.mount("/static", StaticFiles(directory="fatcat_scholar/static"), name="static") + -- cgit v1.2.3