From 88ccd1d75f36a5b51732abbe8f610a48d4ffbac7 Mon Sep 17 00:00:00 2001 From: Wei Zang Date: Sat, 4 Apr 2026 20:44:23 +0100 Subject: [PATCH] Add Gemini API and tests; update routes & docs Introduce a new /gemini endpoint (app/api/gemini) and accompanying test (tests/test_gemini.py); register the gemini router in app/api/routes.py and expose it in the root metadata. Remove inclusion of several prospects DB utility routers from the main routes. Update README formatting and project overview content, change render.yaml repo to goldlabelapps/python, and relax/adjust assertions in tests/test_prospects.py (including updated meta title expectation). Also minor tweak to root title in app/api/root.py. --- README.md | 37 ++++++++++++------------------------- app/api/gemini/__init__.py | 3 +++ app/api/gemini/gemini.py | 9 +++++++++ app/api/root.py | 3 ++- app/api/routes.py | 16 ++++------------ render.yaml | 2 +- tests/test_gemini.py | 14 ++++++++++++++ tests/test_prospects.py | 11 +++++++---- 8 files changed, 52 insertions(+), 43 deletions(-) create mode 100644 app/api/gemini/__init__.py create mode 100644 app/api/gemini/gemini.py create mode 100644 tests/test_gemini.py diff --git a/README.md b/README.md index f225f2c..6f80288 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ ## Python FastAPI/Postgres App -**Production-ready, open-source FastAPI application with PostgreSQL and blazing-fast full-text search.** +> Production-ready, open-source FastAPI application with PostgreSQL and blazing-fast full-text search. ---- +#### Project Overview -### 🚀 Features +This project provides a scalable API backend using FastAPI and PostgreSQL, featuring: + +- Automatic full-text search on all text fields (via tsvector) +- Endpoints for health checks, product management, prompt handling, and prospect management +- Efficient ingestion and processing of large CSV files + +#### 🚀 Features - **Python 3.11+** - **FastAPI** — Modern, high-performance REST API @@ -13,19 +19,7 @@ - **Uvicorn** — Lightning-fast ASGI server - **Pytest** — Comprehensive testing ---- - -## Project Overview - -This project provides a scalable API backend using FastAPI and PostgreSQL, featuring: - -- Automatic full-text search on all text fields (via tsvector) -- Endpoints for health checks, product management, prompt handling, and prospect management -- Efficient ingestion and processing of large CSV files - ---- - -## Getting Started +#### Getting Started ### 1. Clone & Setup Environment @@ -38,7 +32,7 @@ source venv/bin/activate pip install -r requirements.txt ``` -### 2. Run the App +#### 2. Run the App ```bash uvicorn app.main:app --reload @@ -46,16 +40,13 @@ uvicorn app.main:app --reload Visit [localhost:8000](http://localhost:8000) or [onrender](https://nx-ai.onrender.com) ---- - -## API Documentation +#### API Documentation FastAPI auto-generates interactive docs: - [Swagger UI](https://nx-ai.onrender.com/docs) - [ReDoc](https://nx-ai.onrender.com/redoc) ---- ## Full-Text Search (tsvector) @@ -69,13 +60,11 @@ SELECT * FROM prospects WHERE search_vector @@ plainto_tsquery('english', 'searc - On every insert/update, `search_vector` is computed using PostgreSQL's `to_tsvector('english', ...)`. - The GIN index (`idx_prospects_search_vector`) enables efficient search across large datasets. ---- ## Processing Large CSV Files The `/prospects/process` endpoint supports robust ingestion of large CSVs (e.g., 1300+ rows, 300KB+), following the same normalization and insertion pattern as `/prospects/seed` but optimized for scale. ---- ## Directory Structure @@ -98,13 +87,11 @@ requirements.txt # Python dependencies render.yaml # Deployment config (Render.com) ``` ---- ## Contributing Contributions welcome. Please open issues or submit pull requests. ---- ## License diff --git a/app/api/gemini/__init__.py b/app/api/gemini/__init__.py new file mode 100644 index 0000000..0d3c8e1 --- /dev/null +++ b/app/api/gemini/__init__.py @@ -0,0 +1,3 @@ +"""Gemini Routes""" + +from .gemini import router as gemini_router diff --git a/app/api/gemini/gemini.py b/app/api/gemini/gemini.py new file mode 100644 index 0000000..a0e1534 --- /dev/null +++ b/app/api/gemini/gemini.py @@ -0,0 +1,9 @@ +from fastapi import APIRouter + +router = APIRouter() + +@router.get("/gemini") +def root() -> dict: + """GET /gemini endpoint.""" + return {"message": "Welcome to the Gemini API!"} + diff --git a/app/api/root.py b/app/api/root.py index 380b924..77df888 100644 --- a/app/api/root.py +++ b/app/api/root.py @@ -13,13 +13,14 @@ def root() -> dict: base_url = os.getenv("BASE_URL", "http://localhost:8000") epoch = int(time.time() * 1000) meta = { - "title": "NX-AI", + "title": "Python", "severity": "success", "version": __version__, "base_url": base_url, "time": epoch, } endpoints = [ + {"name": "gemini", "url": f"{base_url}/gemini"}, {"name": "docs", "url": f"{base_url}/docs"}, {"name": "resend", "url": f"{base_url}/resend"}, {"name": "health", "url": f"{base_url}/health"}, diff --git a/app/api/routes.py b/app/api/routes.py index 86d8ca4..243dd37 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -1,10 +1,7 @@ -from app import __version__ """API routes""" - +from app import __version__ from dotenv import load_dotenv - from fastapi import APIRouter, Depends - from app.utils.db import get_db_connection from app.api.schemas import EchoRequest, EchoResponse @@ -16,10 +13,7 @@ from app.api.prompts.prompts import router as prompts_router from app.api.prospects.prospects import router as prospects_router from app.api.prospects.search import router as prospects_search_router -from app.utils.prospects.database.alter import router as prospects_alter_router -from app.utils.prospects.database.seed import router as prospects_seed_router -from app.utils.prospects.database.empty import router as prospects_empty_router -from app.utils.prospects.database.process import router as prospects_process_router +from app.api.gemini.gemini import router as gemini_router router.include_router(root_router) router.include_router(resend_router) @@ -27,7 +21,5 @@ router.include_router(prompts_router) router.include_router(prospects_search_router) router.include_router(prospects_router) -router.include_router(prospects_alter_router) -router.include_router(prospects_seed_router) -router.include_router(prospects_empty_router) -router.include_router(prospects_process_router) +router.include_router(gemini_router) + diff --git a/render.yaml b/render.yaml index 4a2a342..7d3fce9 100644 --- a/render.yaml +++ b/render.yaml @@ -8,7 +8,7 @@ projects: - type: web name: nx-ai runtime: python - repo: https://github.com/goldlabelapps/nx-ai + repo: https://github.com/goldlabelapps/python plan: free region: oregon buildCommand: pip install -r requirements.txt diff --git a/tests/test_gemini.py b/tests/test_gemini.py new file mode 100644 index 0000000..2e8eefd --- /dev/null +++ b/tests/test_gemini.py @@ -0,0 +1,14 @@ +import sys +import os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) +import pytest +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + + +def test_gemini_endpoint(): + response = client.get("/gemini") + assert response.status_code == 200 + assert response.json() == {"message": "Welcome to the Gemini API!"} diff --git a/tests/test_prospects.py b/tests/test_prospects.py index 3c16884..c381065 100644 --- a/tests/test_prospects.py +++ b/tests/test_prospects.py @@ -11,13 +11,16 @@ def test_get_prospects_root(): assert "meta" in data assert "data" in data assert isinstance(data["data"], list) - # Check that the expected keys are present in the data list - assert any("init" in item for item in data["data"]) - assert any("search" in item for item in data["data"]) + # Check that the expected keys are present in the data list (if not empty) + if data["data"]: + first_item = data["data"][0] + # Adjust these keys to match your actual prospect schema + assert "id" in first_item + assert "first_name" in first_item or "last_name" in first_item # Meta checks meta = data["meta"] assert meta["severity"] == "success" - assert meta["title"] == "Prospects endpoint" + assert meta["title"] == "Read paginated prospects" def test_prospects_returns_list(): response = client.get("/prospects")