[nominatim-web/fastapi] Initial commit of FastAPI rewrite
- From: Bartłomiej Piotrowski <bpiotrowski src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [nominatim-web/fastapi] Initial commit of FastAPI rewrite
- Date: Fri, 2 Apr 2021 10:59:07 +0000 (UTC)
commit 7e60663987cf7579bc4271f12b45c70aed056a32
Author: Bartłomiej Piotrowski <bpiotrowski gnome org>
Date: Fri Apr 2 12:58:50 2021 +0200
Initial commit of FastAPI rewrite
.gitignore | 1 +
Dockerfile | 26 ++++++++--
app/main.py | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++
docker-compose.yml | 24 +++++++++
nginx.conf | 93 ---------------------------------
redis.lua | 43 ----------------
requirements.txt | 15 ++++++
7 files changed, 211 insertions(+), 139 deletions(-)
---
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bee8a64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/Dockerfile b/Dockerfile
index 37b7b32..848bdb0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,3 +1,23 @@
-FROM openresty/openresty:1.15.8.3-2-buster
-ADD nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
-ADD redis.lua /usr/local/openresty/nginx/redis.lua
+FROM python:3.9 as builder
+
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends \
+ build-essential libpq-dev
+
+ADD requirements.txt /requirements.txt
+RUN python -m venv /venv && \
+ /venv/bin/pip install -r requirements.txt \
+ && rm -f /requirements.txt
+
+FROM python:3.9-slim
+
+EXPOSE 8000
+
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends libpq5 curl && \
+ apt-get clean && rm -rf /var/lib/apt/lists/*
+
+COPY ./app /app
+COPY --from=builder /venv /venv
+
+CMD ["/venv/bin/uvicorn", "--app-dir", "/", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
diff --git a/app/main.py b/app/main.py
new file mode 100644
index 0000000..473cad5
--- /dev/null
+++ b/app/main.py
@@ -0,0 +1,148 @@
+from fastapi import FastAPI, Request, Response
+import redis
+import requests
+import hashlib
+import psycopg2
+import psycopg2.pool
+import datetime
+from pydantic import BaseSettings
+
+
+class Settings(BaseSettings):
+ api_url: str = "https://nominatim.openstreetmap.org"
+ redis_host: str = "localhost"
+ redis_port: int = 6379
+ postgres_url: str = "dbname=nominatim host=127.0.0.1 user=postgres"
+
+
+settings = Settings()
+
+redis_conn = redis.Redis(
+ host=settings.redis_host, port=settings.redis_port, decode_responses=True
+)
+postgres = psycopg2.pool.SimpleConnectionPool(1, 10, settings.postgres_url)
+
+app = FastAPI()
+
+
+@app.on_event("startup")
+def initialize():
+ with postgres.getconn() as conn:
+ with conn.cursor() as cur:
+ cur.execute(
+ "CREATE TABLE IF NOT EXISTS search ( key VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMP
NOT NULL, value text);"
+ )
+
+ cur.execute(
+ "CREATE TABLE IF NOT EXISTS reverse ( key VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMP
NOT NULL, value text);"
+ )
+ conn.commit()
+
+
+@app.get("/status")
+def healthcheck():
+ return {"status": "OK"}
+
+
+@app.get("/search")
+def search(request: Request):
+ lang = request.query_params["accept-language"]
+ limit = request.query_params["limit"]
+ country = request.query_params["country"]
+ state = request.query_params["state"]
+ city = request.query_params["city"]
+ street = request.query_params["street"]
+
+ location = hashlib.md5(f"{country}:{state}:{city}:{street}".encode()).hexdigest()
+
+ if query := request.query_params.get("q"):
+ query = hashlib.md5(query.encode()).hexdigest()
+ else:
+ query = "none"
+
+ key = f"search:{lang}:{limit}:{location}:{query}"
+
+ if resp := redis_conn.get(key):
+ return Response(content=resp, media_type="application/json")
+
+ with postgres.getconn() as conn:
+ with conn.cursor() as cur:
+ cur.execute("SELECT * FROM search WHERE key = %s", (key,))
+ if resp := cur.fetchone():
+ redis_conn.setex(key, 600, resp[2])
+ return Response(content=resp[2], media_type="application/json")
+
+ r = requests.get(f"{settings.api_url}/search", params=request.query_params)
+ redis_conn.setex(key, 600, r.text)
+
+ if r.status_code == 200:
+ with postgres.getconn() as conn:
+ with conn.cursor() as cur:
+ now = datetime.datetime.now()
+ cur.execute(
+ "INSERT INTO search(key,created_at,value) VALUES (%s, %s, %s)",
+ (key, now, r.text),
+ )
+ conn.commit()
+
+ return Response(
+ content=r.text, media_type="application/json", status_code=r.status_code
+ )
+
+
+@app.get("/reverse")
+def reverse(request: Request):
+ lat = "{:.5f}".format(float(request.query_params["lat"]))
+ lon = "{:.5f}".format(float(request.query_params["lon"]))
+ lang = request.query_params["accept-language"]
+
+ key = f"rev:{lang}:{lat}x{lon}"
+ if resp := redis_conn.get(key):
+ return Response(content=resp, media_type="application/json")
+
+ with postgres.getconn() as conn:
+ with conn.cursor() as cur:
+ cur.execute("SELECT * FROM reverse WHERE key = %s", (key,))
+ if resp := cur.fetchone():
+ redis_conn.setex(key, 600, resp[2])
+ return Response(content=resp[2], media_type="application/json")
+
+ query_params = {
+ "lat": lat,
+ "lon": lon,
+ "addressdetails": 1,
+ "accept-language": lang,
+ "format": "json",
+ }
+
+ r = requests.get(f"{settings.api_url}/reverse", params=query_params)
+ redis_conn.setex(key, 600, r.text)
+
+ if r.status_code == 200:
+ with postgres.getconn() as conn:
+ with conn.cursor() as cur:
+ now = datetime.datetime.now()
+ cur.execute(
+ "INSERT INTO reverse(key,created_at,value) VALUES (%s, %s, %s)",
+ (key, now, r.text),
+ )
+ conn.commit()
+
+ return Response(
+ content=r.text, media_type="application/json", status_code=r.status_code
+ )
+
+
+@app.get("/expire")
+def expire():
+ limit = datetime.datetime.now() - datetime.timedelta(days=30)
+
+ with postgres.getconn() as conn:
+ with conn.cursor() as cur:
+ cur.execute("DELETE FROM search WHERE created_at < %s", (limit,))
+ search = cur.rowcount
+ cur.execute("DELETE FROM reverse WHERE created_at < %s", (limit,))
+ reverse = cur.rowcount
+ conn.commit()
+
+ return {"search": search, "reverse": reverse}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..22c0383
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,24 @@
+version: "2"
+services:
+ redis:
+ image: redis:6.2.1
+
+ postgres:
+ image: postgres
+ environment:
+ - POSTGRES_DB=nominatim
+ - POSTGRES_USER=postgres
+ - POSTGRES_HOST_AUTH_METHOD=trust
+
+ backend:
+ build:
+ dockerfile: Dockerfile
+ context: .
+ ports:
+ - "8000:8000"
+ links:
+ - redis:redis
+ - postgres:postgres
+ depends_on:
+ - redis
+ - postgres
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..fd7bdd0
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,15 @@
+certifi==2020.12.5
+chardet==4.0.0
+click==7.1.2
+fastapi==0.63.0
+h11==0.12.0
+hiredis==2.0.0
+idna==2.10
+psycopg2==2.8.6
+pydantic==1.8.1
+redis==3.5.3
+requests==2.25.1
+starlette==0.13.6
+typing-extensions==3.7.4.3
+urllib3==1.26.4
+uvicorn==0.13.4
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]