Skip to content

Commit 7cb2006

Browse files
Merge pull request #21 from goldlabelapps/staging
This pull request refactors the product seeding and retrieval logic in the API to support a more flexible, CSV-driven schema for the `product` table.
2 parents e380a4e + c02ff51 commit 7cb2006

8 files changed

Lines changed: 98 additions & 153 deletions

File tree

app/api/products/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
from .products import router
2-
from .reset import router as reset_router
1+
from .products import router as products_router
2+
from .seed import router as seed_router

app/api/products/products.py

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,27 @@
1+
12
from app import __version__
23
from fastapi import APIRouter
3-
from fastapi import status
44
import os, time
5-
import psycopg2
6-
from dotenv import load_dotenv
7-
from app import __version__
5+
from app.api.db import get_db_connection
86

97
router = APIRouter()
108

119
@router.get("/products")
1210
def root() -> dict:
13-
"""Return a structured welcome message for the API root, including product data."""
14-
load_dotenv()
15-
conn = psycopg2.connect(
16-
host=os.getenv('DB_HOST'),
17-
port=os.getenv('DB_PORT', '5432'),
18-
dbname=os.getenv('DB_NAME'),
19-
user=os.getenv('DB_USER'),
20-
password=os.getenv('DB_PASSWORD')
21-
)
11+
"""Return all products with full CSV-based schema."""
12+
conn_gen = get_db_connection()
13+
conn = next(conn_gen)
2214
cur = conn.cursor()
23-
cur.execute('SELECT id, name, description, price, in_stock, created_at FROM product;')
24-
products = [
25-
{
26-
"id": row[0],
27-
"name": row[1],
28-
"description": row[2],
29-
"price": float(row[3]),
30-
"in_stock": row[4],
31-
"created_at": row[5].isoformat() if row[5] else None
32-
}
33-
for row in cur.fetchall()
34-
]
15+
cur.execute('SELECT * FROM product;')
16+
if cur.description is None:
17+
products = []
18+
else:
19+
columns = [desc[0] for desc in cur.description]
20+
products = [dict(zip(columns, row)) for row in cur.fetchall()]
3521
cur.close()
3622
conn.close()
3723

38-
load_dotenv()
3924
base_url = os.getenv("BASE_URL", "http://localhost:8000")
40-
4125
epoch = int(time.time() * 1000)
4226
meta = {
4327
"severity": "success",

app/api/products/reset.py

Lines changed: 0 additions & 103 deletions
This file was deleted.

app/api/products/seed.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from app import __version__
2+
import os, time
3+
import csv
4+
from fastapi import APIRouter, status
5+
from app.api.db import get_db_connection
6+
7+
router = APIRouter()
8+
9+
CSV_PATH = os.path.join(os.path.dirname(__file__), 'start_data.csv')
10+
CSV_PATH = os.path.abspath(CSV_PATH)
11+
12+
@router.get("/products/seed", status_code=status.HTTP_200_OK)
13+
def seed_products() -> dict:
14+
"""Delete and recreate the product table, then seed with CSV data."""
15+
conn_gen = get_db_connection()
16+
conn = next(conn_gen)
17+
cur = conn.cursor()
18+
# Drop and recreate table with all CSV columns
19+
cur.execute('''
20+
DROP TABLE IF EXISTS product;
21+
CREATE TABLE product (
22+
id SERIAL PRIMARY KEY,
23+
Params TEXT,
24+
item INTEGER,
25+
title TEXT,
26+
UOS TEXT,
27+
Pack_Description TEXT,
28+
Hierarchy1 TEXT,
29+
Hierarchy2 TEXT,
30+
Hierarchy3 TEXT,
31+
UOP TEXT,
32+
sSell1 NUMERIC(10,2),
33+
sSell2 NUMERIC(10,2),
34+
sSell3 NUMERIC(10,2),
35+
sSell4 NUMERIC(10,2),
36+
sSell5 NUMERIC(10,2),
37+
pack1 INTEGER,
38+
pack2 INTEGER,
39+
pack3 INTEGER,
40+
pack4 INTEGER,
41+
pack5 INTEGER,
42+
EAN TEXT
43+
);
44+
''')
45+
# Read and insert CSV data
46+
with open(CSV_PATH, newline='') as csvfile:
47+
reader = csv.DictReader(csvfile)
48+
rows = [row for row in reader]
49+
for row in rows:
50+
# Map 'desc' from CSV to 'title' for DB
51+
row['title'] = row.pop('desc')
52+
cur.execute(
53+
"""
54+
INSERT INTO product (
55+
Params, item, title, UOS, Pack_Description, Hierarchy1, Hierarchy2, Hierarchy3, UOP,
56+
sSell1, sSell2, sSell3, sSell4, sSell5, pack1, pack2, pack3, pack4, pack5, EAN
57+
) VALUES (
58+
%(Params)s, %(item)s, %(title)s, %(UOS)s, %(Pack_Description)s, %(Hierarchy1)s, %(Hierarchy2)s, %(Hierarchy3)s, %(UOP)s,
59+
%(sSell1)s, %(sSell2)s, %(sSell3)s, %(sSell4)s, %(sSell5)s, %(pack1)s, %(pack2)s, %(pack3)s, %(pack4)s, %(pack5)s, %(EAN)s
60+
)
61+
""",
62+
row
63+
)
64+
conn.commit()
65+
cur.execute('SELECT * FROM product;')
66+
if cur.description is None:
67+
products = []
68+
else:
69+
columns = [desc[0] for desc in cur.description]
70+
products = [dict(zip(columns, row)) for row in cur.fetchall()]
71+
cur.close()
72+
conn.close()
73+
74+
base_url = os.getenv("BASE_URL", "http://localhost:8000")
75+
epoch = int(time.time() * 1000)
76+
meta = {
77+
"severity": "success",
78+
"title": "Product table seeded",
79+
"version": __version__,
80+
"base_url": base_url,
81+
"time": epoch,
82+
}
83+
return {"meta": meta, "data": products}

app/api/routes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
from app.api.root import router as root_router
1414
from app.api.health import router as health_router
1515
from app.api.products.products import router as products_router
16-
from app.api.products.reset import router as reset_router
16+
from app.api.products.seed import router as seed_router
1717

1818
router.include_router(root_router)
1919
router.include_router(health_router)
2020
router.include_router(products_router)
21-
router.include_router(reset_router)
21+
router.include_router(seed_router)

app/static/favicon.ico

100644100755
-13.6 KB
Binary file not shown.

tests/test_routes.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,3 @@ def test_health_returns_ok() -> None:
2525
assert response.status_code == 200
2626
assert response.json() == {"status": "ok"}
2727

28-
29-
def test_echo_returns_message() -> None:
30-
"""POST /echo should return the provided message."""
31-
response = client.post("/echo", json={"message": "hello"})
32-
assert response.status_code == 200
33-
assert response.json() == {"echo": "hello"}
34-
35-
36-
def test_echo_empty_string() -> None:
37-
"""POST /echo with an empty string should echo back an empty string."""
38-
response = client.post("/echo", json={"message": ""})
39-
assert response.status_code == 200
40-
assert response.json() == {"echo": ""}
41-
42-
43-
def test_echo_missing_body_returns_422() -> None:
44-
"""POST /echo without a body should return 422 Unprocessable Entity."""
45-
response = client.post("/echo", json={})
46-
assert response.status_code == 422

0 commit comments

Comments
 (0)