Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 12 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand All @@ -38,24 +32,21 @@ source venv/bin/activate
pip install -r requirements.txt
```

### 2. Run the App
#### 2. Run the App

```bash
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)

Expand All @@ -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

Expand All @@ -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

Expand Down
3 changes: 3 additions & 0 deletions app/api/gemini/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Gemini Routes"""

from .gemini import router as gemini_router
9 changes: 9 additions & 0 deletions app/api/gemini/gemini.py
Original file line number Diff line number Diff line change
@@ -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!"}

3 changes: 2 additions & 1 deletion app/api/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down
16 changes: 4 additions & 12 deletions app/api/routes.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -16,18 +13,13 @@
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)
router.include_router(health_router)
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)

2 changes: 1 addition & 1 deletion render.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions tests/test_gemini.py
Original file line number Diff line number Diff line change
@@ -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!"}
11 changes: 7 additions & 4 deletions tests/test_prospects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading