-
Notifications
You must be signed in to change notification settings - Fork 0
This pull request introduces PostgreSQL database integration for product data management and retrieval in the NX AI FastAPI application. The main changes include adding scripts for database setup and testing, modifying the root API endpoint to return product data, and updating documentation and metadata for clarity. #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4f22a9b
a9b8993
7d98eaf
6e62128
80815bd
5d0c92c
1d445d3
0a8f519
c07b4ea
8fcb9e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| """NX AI - FastAPI""" | ||
|
|
||
| # Version tracking | ||
| __version__ = "1.0.0" | ||
| __version__ = "1.0.2" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,35 @@ | ||
| """API route definitions for NX AI.""" | ||
|
|
||
| from fastapi import APIRouter | ||
| import os | ||
| import time | ||
|
|
||
| import psycopg2 | ||
| from dotenv import load_dotenv | ||
| from fastapi import APIRouter, Depends | ||
| from pydantic import BaseModel | ||
|
|
||
| from app import __version__ | ||
|
|
||
| load_dotenv() | ||
|
|
||
| router = APIRouter() | ||
|
|
||
|
|
||
| def get_db_connection(): # type: ignore[return] | ||
| """Create and yield a PostgreSQL connection for use as a FastAPI dependency.""" | ||
| conn = psycopg2.connect( | ||
| host=os.getenv('DB_HOST'), | ||
| port=os.getenv('DB_PORT', '5432'), | ||
| dbname=os.getenv('DB_NAME'), | ||
| user=os.getenv('DB_USER'), | ||
| password=os.getenv('DB_PASSWORD'), | ||
| ) | ||
| try: | ||
| yield conn | ||
| finally: | ||
| conn.close() | ||
|
|
||
|
|
||
| class EchoRequest(BaseModel): | ||
| """Request body for the echo endpoint.""" | ||
|
|
||
|
|
@@ -18,23 +42,34 @@ class EchoResponse(BaseModel): | |
| echo: str | ||
|
|
||
|
|
||
|
|
||
| import time | ||
| import sys | ||
| from app import __version__ | ||
|
|
||
| @router.get("/") | ||
| def root() -> dict: | ||
| """Return a structured welcome message for the API root.""" | ||
| def root(conn=Depends(get_db_connection)) -> dict: | ||
| """Return a structured welcome message for the API root, including product data.""" | ||
| cur = conn.cursor() | ||
| try: | ||
| cur.execute('SELECT id, name, description, price, in_stock, created_at FROM product;') | ||
| products = [ | ||
| { | ||
| "id": row[0], | ||
| "name": row[1], | ||
| "description": row[2], | ||
| "price": str(row[3]) if row[3] is not None else None, | ||
| "in_stock": row[4], | ||
| "created_at": row[5].isoformat() if row[5] else None, | ||
| } | ||
| for row in cur.fetchall() | ||
| ] | ||
| finally: | ||
| cur.close() | ||
| epoch = int(time.time() * 1000) | ||
| meta = { | ||
| "version": __version__, | ||
| "time": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), | ||
| "epoch": epoch, | ||
| "severity": "success", | ||
| "message": "NX AI says hello.", | ||
| "message": f"NX AI says hello. Returned {len(products)} products.", | ||
| } | ||
| return {"meta": meta} | ||
| return {"meta": meta, "data": products} | ||
|
||
|
|
||
|
|
||
| @router.get("/health") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,12 +2,13 @@ | |
|
|
||
| from fastapi import FastAPI | ||
|
|
||
| from app import __version__ | ||
| from app.api.routes import router | ||
|
|
||
| app = FastAPI( | ||
| title="NX AI", | ||
| description="A clean, modular FastAPI application for AI services.", | ||
| version="1.0.0", | ||
| description="Production-ready Python FastAPI app for NX", | ||
| version=__version__, | ||
| ) | ||
|
Comment on lines
8
to
12
|
||
|
|
||
| app.include_router(router) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import os | ||
|
|
||
| import psycopg2 | ||
| from dotenv import load_dotenv | ||
|
|
||
|
|
||
| def main() -> None: | ||
| load_dotenv() | ||
|
|
||
| conn = psycopg2.connect( | ||
| host=os.getenv('DB_HOST'), | ||
| port=os.getenv('DB_PORT', '5432'), | ||
| dbname=os.getenv('DB_NAME'), | ||
| user=os.getenv('DB_USER'), | ||
| password=os.getenv('DB_PASSWORD'), | ||
| ) | ||
| cur = conn.cursor() | ||
| try: | ||
| cur.execute('SELECT * FROM product;') | ||
| rows = cur.fetchall() | ||
| for row in rows: | ||
| print(row) | ||
| finally: | ||
| cur.close() | ||
| conn.close() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import os | ||
|
|
||
| import psycopg2 | ||
| from dotenv import load_dotenv | ||
|
|
||
|
|
||
| def main() -> None: | ||
| load_dotenv() | ||
|
|
||
| conn = psycopg2.connect( | ||
| host=os.getenv('DB_HOST'), | ||
| port=os.getenv('DB_PORT', '5432'), | ||
| dbname=os.getenv('DB_NAME'), | ||
| user=os.getenv('DB_USER'), | ||
| password=os.getenv('DB_PASSWORD'), | ||
| ) | ||
| cur = conn.cursor() | ||
| try: | ||
| # Create product table with a unique constraint on name for idempotent seeding | ||
| cur.execute(''' | ||
| CREATE TABLE IF NOT EXISTS product ( | ||
| id SERIAL PRIMARY KEY, | ||
| name VARCHAR(100) NOT NULL UNIQUE, | ||
| description TEXT, | ||
| price NUMERIC(10, 2) NOT NULL, | ||
| in_stock BOOLEAN DEFAULT TRUE, | ||
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | ||
| ); | ||
| ''') | ||
|
|
||
| # Insert seed data; skip rows whose name already exists | ||
| cur.execute(''' | ||
| INSERT INTO product (name, description, price, in_stock) VALUES | ||
| ('Widget', 'A useful widget', 19.99, TRUE), | ||
| ('Gadget', 'A fancy gadget', 29.99, TRUE), | ||
| ('Thingamajig', 'An interesting thingamajig', 9.99, FALSE) | ||
| ON CONFLICT (name) DO NOTHING; | ||
| ''') | ||
|
|
||
| conn.commit() | ||
| print("Product table created and seeded.") | ||
| finally: | ||
| cur.close() | ||
| conn.close() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import os | ||
| import sys | ||
|
|
||
| import psycopg2 | ||
| from dotenv import load_dotenv | ||
|
|
||
|
|
||
| def main() -> int: | ||
| load_dotenv() | ||
|
|
||
| db_host = os.getenv('DB_HOST') | ||
| db_port = os.getenv('DB_PORT', '5432') | ||
| db_name = os.getenv('DB_NAME') | ||
| db_user = os.getenv('DB_USER') | ||
| db_password = os.getenv('DB_PASSWORD') | ||
|
|
||
| print("Attempting connection with:") | ||
| print(f"Host: {db_host}") | ||
| print(f"Port: {db_port}") | ||
| print(f"Database: {db_name}") | ||
| print(f"User: {db_user}") | ||
|
|
||
| try: | ||
| conn = psycopg2.connect( | ||
| host=db_host, | ||
| port=db_port, | ||
| dbname=db_name, | ||
| user=db_user, | ||
| password=db_password, | ||
| ) | ||
| print("Connection successful!") | ||
| conn.close() | ||
| return 0 | ||
| except Exception as e: | ||
| print(f"Connection failed: {e}") | ||
| return 1 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This module now hard-depends on
python-dotenvandpsycopg2, but the repo'srequirements.txtcurrently doesn't include them. Add the corresponding dependencies (e.g.,python-dotenvandpsycopg2-binary/psycopg2) so imports don't fail at runtime.