Skip to content

Commit 8c1bd48

Browse files
committed
merge fast-api
2 parents b2caedc + 5fa71fd commit 8c1bd48

File tree

7 files changed

+633
-72
lines changed

7 files changed

+633
-72
lines changed

Dockerfile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ RUN apt-get update && \
44
apt-get install -y make && \
55
pip install uv
66

7-
ENV UV_COMPILE_BYTECODE=1
8-
97
WORKDIR /app
108
COPY . .
119
RUN make install

Makefile

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@ install:
44
uv sync
55

66
dev:
7-
uv run flask --app app.server run --debug -h 0.0.0.0 -p $(PORT)
7+
uv run fastapi dev app/server.py --host 0.0.0.0 --port $(PORT)
88

99
run:
10-
uv run gunicorn -w 4 -b 0.0.0.0:$(PORT) app.server:app
10+
uv run uvicorn --workers 4 --host 0.0.0.0 --port $(PORT) app.server:app
1111

1212
build:
1313
docker build . --tag=data-charts-api
1414

1515
start:
16-
-docker stop data-charts-api
17-
-docker rm data-charts-api
16+
-docker stop data-charts-api || true
17+
-docker rm data-charts-api || true
1818
docker run -d --name data-charts-api -p $(PORT):$(PORT) data-charts-api
1919

2020
stop:
21-
-docker stop data-charts-api
21+
-docker stop data-charts-api || true
2222

2323
rm:
24-
-docker rm data-charts-api
24+
-docker rm data-charts-api || true
2525

2626
bash:
2727
docker run --rm -it data-charts-api bash

app/server.py

Lines changed: 70 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,88 @@
11
import logging
22
import os
3-
import traceback
3+
from contextlib import asynccontextmanager
44
from datetime import datetime
55

6-
import psycopg
6+
import asyncpg
77
from dotenv import load_dotenv
8-
from flask import Flask, request
9-
from psycopg.rows import dict_row
8+
from fastapi import Depends, FastAPI, HTTPException, Query
9+
from fastapi.middleware.gzip import GZipMiddleware
10+
from fastapi_cache import FastAPICache
11+
from fastapi_cache.backends.inmemory import InMemoryBackend
12+
from fastapi_cache.decorator import cache
1013

1114
load_dotenv()
1215

16+
DATABASE_URL = os.getenv('DATABASE_URL')
1317

14-
app = Flask(__name__)
15-
app.config['DATABASE_URL'] = os.getenv('DATABASE_URL')
18+
@asynccontextmanager
19+
async def lifespan(app: FastAPI):
20+
app.state.pool = await asyncpg.create_pool(
21+
DATABASE_URL,
22+
min_size=5,
23+
max_size=20
24+
)
25+
FastAPICache.init(InMemoryBackend())
26+
yield
27+
await app.state.pool.close()
1628

29+
app = FastAPI(lifespan=lifespan)
30+
app.add_middleware(GZipMiddleware, minimum_size=1000)
1731

18-
def get_db():
19-
return psycopg.connect(app.config['DATABASE_URL'])
32+
async def get_db():
33+
async with app.state.pool.acquire() as conn:
34+
yield conn
2035

2136

22-
@app.route('/')
23-
def index():
24-
return 'It Works'
37+
@app.get("/")
38+
async def index():
39+
return {"status": "It Works"}
2540

2641

27-
@app.get('/visits')
28-
def get_visits():
29-
query = request.args
42+
@app.get("/visits")
43+
@cache(expire=300)
44+
async def get_visits(
45+
begin: str = Query(..., description="Start date in ISO format"),
46+
end: str = Query(..., description="End date in ISO format"),
47+
db = Depends(get_db)
48+
):
49+
begin = datetime.fromisoformat(begin)
50+
end = datetime.fromisoformat(end)
3051
try:
31-
begin_date = datetime.fromisoformat(query['begin'])
32-
end_date = datetime.fromisoformat(query['end'])
33-
except ValueError as e:
34-
logging.error(traceback.format_exc(2, chain=False))
35-
return 'Invalid date. Please check your input', 400
36-
query = '''
37-
SELECT *
38-
FROM visits
39-
WHERE visits.datetime BETWEEN (%s) AND (%s);'''
40-
with get_db() as conn:
41-
with conn.cursor(row_factory=dict_row) as c:
42-
c.execute(query, [begin_date, end_date])
43-
res = c.fetchall()
44-
return res
52+
query = """
53+
SELECT *
54+
FROM visits
55+
WHERE visits.datetime BETWEEN $1 AND $2
56+
"""
57+
records = await db.fetch(query, begin, end)
58+
return records
59+
except Exception as e:
60+
logging.error(f"Error fetching visits: {str(e)}")
61+
raise HTTPException(
62+
status_code=500,
63+
detail="Internal server error occurred while fetching visits"
64+
)
4565

46-
47-
@app.get('/registrations')
48-
def get_registrations():
49-
query = request.args
66+
@app.get("/registrations")
67+
@cache(expire=300)
68+
async def get_registrations(
69+
begin: str = Query(..., description="Start date in ISO format"),
70+
end: str = Query(..., description="End date in ISO format"),
71+
db = Depends(get_db)
72+
):
73+
begin = datetime.fromisoformat(begin)
74+
end = datetime.fromisoformat(end)
5075
try:
51-
begin_date = datetime.fromisoformat(query['begin'])
52-
end_date = datetime.fromisoformat(query['end'])
53-
except ValueError as e:
54-
logging.error(traceback.format_exc(2, chain=False))
55-
return 'Invalid date. Please check your input', 400
56-
query = '''
57-
SELECT *
58-
FROM registrations
59-
WHERE registrations.datetime BETWEEN (%s) AND (%s);'''
60-
with get_db() as conn:
61-
with conn.cursor(row_factory=dict_row) as c:
62-
c.execute(query, [begin_date, end_date])
63-
res = c.fetchall()
64-
return res
76+
query = """
77+
SELECT *
78+
FROM registrations
79+
WHERE registrations.datetime BETWEEN $1 AND $2
80+
"""
81+
records = await db.fetch(query, begin, end)
82+
return records
83+
except Exception as e:
84+
logging.error(f"Error fetching registrations: {str(e)}")
85+
raise HTTPException(
86+
status_code=500,
87+
detail="Internal server error occurred while fetching registrations"
88+
)

docker-compose.yml

Whitespace-only changes.

pyproject.toml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ description = "api for data charts project"
55
readme = "README.md"
66
requires-python = ">=3.12"
77
dependencies = [
8+
"asyncpg>=0.30.0",
9+
"fastapi-cache2>=0.2.2",
10+
"fastapi[standard]>=0.115.4",
811
"flask>=3.0.3",
9-
"gunicorn>=23.0.0",
1012
"psycopg[binary]>=3.2.3",
1113
"python-dotenv>=1.0.1",
14+
"uvicorn>=0.32.0",
1215
]
1316

1417
[tool.uv]
@@ -17,10 +20,3 @@ dev-dependencies = [
1720
"requests>=2.32.3",
1821
"ruff>=0.7.2",
1922
]
20-
21-
[tool.hatch.build.targets.wheel]
22-
packages = ["app"]
23-
24-
[build-system]
25-
requires = ["hatchling"]
26-
build-backend = "hatchling.build"

tests/test_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
def test_server():
88
response = requests.get('http://localhost:4000/')
99
assert response.status_code == 200
10-
assert response.text == 'It Works'
10+
assert response.json()['status'] == 'It Works'
1111

1212
response = requests.get('http://localhost:4000/visits?begin=2023-03-01&end=2023-03-02')
1313
assert response.status_code == 200
1414
assert response.json()[0] == {
15-
"datetime": "Wed, 01 Mar 2023 10:36:22 GMT",
15+
"datetime": "2023-03-01T10:36:22",
1616
"platform": "web",
1717
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.6",
1818
"visit_id": "1de9ea66-70d3-4a1f-8735-df5ef7697fb9"

0 commit comments

Comments
 (0)