Flask -> FastAPI rewrite

This commit is contained in:
Eriks Karls
2023-12-14 18:57:31 +02:00
parent 91e8f7bbf4
commit 3f322e2999
19 changed files with 664 additions and 207 deletions

0
service/__init__.py Normal file
View File

123
service/app.py Normal file
View File

@ -0,0 +1,123 @@
import datetime
import json
import uuid
from collections import defaultdict
from io import BytesIO
from typing import Annotated
from fastapi import FastAPI, Form, HTTPException, responses
from icalendar import Alarm, Calendar, Event
from pydantic import BaseModel
from unidecode import unidecode
from uvicorn.workers import UvicornWorker
def generate_ical_for_mapping(cal: dict[datetime.date, list[str]]) -> BytesIO:
ical = Calendar()
ical["VERSION"] = "2.0"
ical["PRODID"] = "NameDays"
for date, names in sorted(cal.items(), key=lambda x: x[0]):
ev = Event()
ev.add("SUMMARY", ", ".join(sorted(names)))
ev.add("DTSTART", date)
ev.add("DTEND", date + datetime.timedelta(days=1))
ev.add("DTSTAMP", datetime.datetime(2000, 1, 1))
ev.add("RRULE", {"FREQ": "YEARLY"})
ev.add("CATEGORY", "Anniversary")
ev.add("UID", uuid.uuid4())
alert = Alarm()
alert.add("action", "DISPLAY")
alert.add("TRIGGER", datetime.timedelta(hours=9))
alert.add("DESCRIPTION", "Default description")
ev.add_component(alert)
ical.add_component(ev)
return BytesIO(ical.to_ical(True))
def starts_with(string_to_check: str, check_string: str) -> bool:
value = unidecode(string_to_check.lower(), errors="preserve")
query = unidecode(check_string.lower(), errors="preserve")
return value.startswith(query)
with open("service/mapping.json") as f:
MAPPING = json.load(f)
with open("service/vardadienas.json") as f:
NAMEDAYS = json.load(f)
LV_MONTHS = {
1: "jan",
2: "feb",
3: "mar",
4: "apr",
5: "mai",
6: "jūn",
7: "jūl",
8: "aug",
9: "sep",
10: "okt",
11: "nov",
12: "dec",
}
class SearchResult(BaseModel):
text: str
id: str
class SearchResultSection(BaseModel):
text: str
children: list[SearchResult]
class SearchResponse(BaseModel):
results: list[SearchResultSection]
pagination: dict[str, bool] = {"more": False}
app = FastAPI()
@app.get("/", response_class=responses.HTMLResponse)
async def index_html():
with open("assets/index.html") as f:
return responses.HTMLResponse(f.read(), 201)
@app.get("/api/search")
async def search_words(term: str) -> SearchResponse:
result_map = {}
for section, names in MAPPING.items():
result_map[section] = []
for key, value in names.items():
if starts_with(value, term):
result_map[section].append(
SearchResult(id=key, text=f"{value} ({key.split('__')[1]}. {LV_MONTHS[int(key.split('__')[0])]}.)")
)
return SearchResponse(
results=[
SearchResultSection(text=section.title(), children=results)
for section, results in result_map.items()
if results
]
)
@app.post("/api/download", response_class=responses.StreamingResponse)
async def download_ical(words: Annotated[list[str], Form()]):
cal = defaultdict(list)
for selected_name in words:
month, day, name = selected_name.split("__")
vdmd = NAMEDAYS[str(int(month))][str(int(day))]
if name in vdmd["normal"] or name in vdmd["special"]:
date = datetime.date(2000, int(month), int(day))
cal[date].append(name)
if cal:
return responses.StreamingResponse(
content=generate_ical_for_mapping(cal),
media_type="text/calendar",
headers={"Content-Disposition": f'attachment; filename="{uuid.uuid4().hex}.ics"'},
)
raise HTTPException(404, "No names have been found!")

92
service/gunicorn.py Normal file
View File

@ -0,0 +1,92 @@
""" Reference: https://docs.gunicorn.org/en/stable/settings.html """
""" Config File https://docs.gunicorn.org/en/stable/settings.html#config-file """
config = "service/gunicorn.py"
wsgi_app = "service.app:app"
""" Debugging https://docs.gunicorn.org/en/stable/settings.html#debugging """
# reload = False
# reload_engine = "auto"
# reload_extra_files = []
# spew = False
# check_config = False
# print_config = False
""" Logging https://docs.gunicorn.org/en/stable/settings.html#logging """
accesslog = "-"
# disable_redirect_access_to_syslog = False
access_log_format = "%(t)s [%({HTTP_X_REAL_IP}e)s] '%(m)s' %(s)s %(b)s '%(U)s' '%(q)s' '%(a)s' '%(D)s'"
# errorlog = "-"
loglevel = "debug"
capture_output = True
# logger_class = 'gunicorn.glogging.Logger'
# logconfig = None
# logconfig_dict = dict()
# logconfig_json = None
# syslog_addr = 'udp://localhost:514'
# syslog = None
# syslog_prefix = None
# syslog_facility = "user"
# enable_stdio_inheritance = False
# statsd_host = None
# dogstatsd_tags = ""
# statsd_prefix = ""
""" Process Naming https://docs.gunicorn.org/en/stable/settings.html#process-naming """
# proc_name = None
# default_proc_name = ""
""" SSL https://docs.gunicorn.org/en/stable/settings.html#ssl """
# keyfile = None
# certfile = None
# ssl_version = 2
# cert_reqs = 0
# ca_certs = None
# suppress_ragged_eofs = True
# do_handshake_on_connect = False
# ciphers = None
""" Security https://docs.gunicorn.org/en/stable/settings.html#security """
# limit_request_line = 4094
# limit_request_fields = 100
# limit_request_field_size = 8190
""" Server Hooks https://docs.gunicorn.org/en/stable/settings.html#server-hooks """
""" Server Mechanics https://docs.gunicorn.org/en/stable/settings.html#server-mechanics """
# preload_app = False
# sendfile = None
# reuse_port = False
chdir = "/app"
# daemon = False
# raw_env = []
# pidfile = None
# worker_tmp_dir = None
# user = "django"
# group = "django"
# umask = 0
# initgroups = False
# tmp_upload_dir = None
# secure_scheme_headers = {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}
# forwarded_allow_ips = ['127.0.0.1']
# pythonpath = None
# paste = None
# proxy_protocol = False
# proxy_allow_ips = ['127.0.0.1']
# raw_paste_global_conf = []
# strip_header_spaces = False
""" Server Socket https://docs.gunicorn.org/en/stable/settings.html#server-socket """
bind = "0.0.0.0:5000"
# backlog = 2048
""" Worker Processes https://docs.gunicorn.org/en/stable/settings.html#worker-processes """
# workers = 1
worker_class = "uvicorn.workers.UvicornWorker"
# threads = 2
# worker_connections = 1000
# max_requests = 0
# max_requests_jitter = 0
# timeout = 30
# graceful_timeout = 30
# keepalive = 2

1
service/mapping.json Normal file

File diff suppressed because one or more lines are too long

7719
service/vardadienas.json Normal file

File diff suppressed because it is too large Load Diff