Flask -> FastAPI rewrite
This commit is contained in:
0
service/__init__.py
Normal file
0
service/__init__.py
Normal file
123
service/app.py
Normal file
123
service/app.py
Normal 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
92
service/gunicorn.py
Normal 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
1
service/mapping.json
Normal file
File diff suppressed because one or more lines are too long
7719
service/vardadienas.json
Normal file
7719
service/vardadienas.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user