Skip to content

Commit 9a5936b

Browse files
committed
rework to fast-api
1 parent 9aca9b6 commit 9a5936b

File tree

6 files changed

+560
-53
lines changed

6 files changed

+560
-53
lines changed

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ 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 granian --interface asgi --workers 10 --host 0.0.0.0 --port $(PORT) app.server:app
1111

1212
start:
1313
make stop rm || true
@@ -34,6 +34,7 @@ test:
3434
uv run pytest; \
3535
status=$$?; \
3636
if [ -f $(PID_FILE) ]; then kill `cat $(PID_FILE)` && rm $(PID_FILE); fi; \
37+
sleep 1; \
3738
exit $$status
3839

3940
lint:

app/server.py

Lines changed: 63 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,79 @@
11
import logging
22
import os
3-
import traceback
3+
from contextlib import asynccontextmanager
44
from datetime import datetime
5+
from typing import List
56

6-
import psycopg
7+
import asyncpg
78
from dotenv import load_dotenv
8-
from flask import Flask, request
9-
from psycopg.rows import dict_row
9+
from fastapi import Depends, FastAPI, HTTPException, Query
1010

1111
load_dotenv()
1212

13+
DATABASE_URL = "postgres://student:[email protected]:5432/chartsdb"
1314

14-
app = Flask(__name__)
15-
app.config['DATABASE_URL'] = os.getenv('DATABASE_URL')
15+
# Менеджер контекста для подключения к БД
16+
@asynccontextmanager
17+
async def lifespan(app: FastAPI):
18+
# Создаем пул соединений при старте
19+
app.state.pool = await asyncpg.create_pool(
20+
DATABASE_URL,
21+
min_size=5,
22+
max_size=20
23+
)
24+
yield
25+
# Закрываем пул при выключении
26+
await app.state.pool.close()
1627

28+
app = FastAPI(lifespan=lifespan)
1729

18-
def get_db():
19-
return psycopg.connect(app.config['DATABASE_URL'])
30+
# Зависимость для получения соединения с БД
31+
async def get_db():
32+
async with app.state.pool.acquire() as conn:
33+
yield conn
2034

35+
@app.get("/")
36+
async def index():
37+
return {"status": "It Works"}
2138

22-
@app.route('/')
23-
def index():
24-
return 'It Works'
25-
26-
27-
@app.get('/visits')
28-
def get_visits():
29-
query = request.args
39+
@app.get("/visits")
40+
async def get_visits(
41+
begin: datetime = Query(..., description="Start date in ISO format"),
42+
end: datetime = Query(..., description="End date in ISO format"),
43+
db = Depends(get_db)
44+
):
3045
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-
conn = get_db()
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
45-
46+
query = """
47+
SELECT *
48+
FROM visits
49+
WHERE visits.datetime BETWEEN $1 AND $2;
50+
"""
51+
records = await db.fetch(query, begin, end)
52+
return records
53+
except Exception as e:
54+
logging.error(f"Error fetching visits: {str(e)}")
55+
raise HTTPException(
56+
status_code=500,
57+
detail="Internal server error occurred while fetching visits"
58+
)
4659

47-
@app.get('/registrations')
48-
def get_registrations():
49-
query = request.args
60+
@app.get("/registrations")
61+
async def get_registrations(
62+
begin: datetime = Query(..., description="Start date in ISO format"),
63+
end: datetime = Query(..., description="End date in ISO format"),
64+
db = Depends(get_db)
65+
):
5066
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-
conn = get_db()
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
67+
query = """
68+
SELECT *
69+
FROM registrations
70+
WHERE registrations.datetime BETWEEN $1 AND $2;
71+
"""
72+
records = await db.fetch(query, begin, end)
73+
return records
74+
except Exception as e:
75+
logging.error(f"Error fetching registrations: {str(e)}")
76+
raise HTTPException(
77+
status_code=500,
78+
detail="Internal server error occurred while fetching registrations"
79+
)

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ description = "api for data charts project"
55
readme = "README.md"
66
requires-python = ">=3.12"
77
dependencies = [
8+
"asyncpg>=0.30.0",
9+
"fastapi[standard]>=0.115.4",
810
"flask>=3.0.3",
11+
"granian>=1.6.3",
912
"gunicorn>=23.0.0",
1013
"psycopg>=3.2.3",
1114
"python-dotenv>=1.0.1",

server.pid

Lines changed: 0 additions & 1 deletion
This file was deleted.

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)