add prod/dev configurations

This commit is contained in:
2025-09-07 16:43:37 -07:00
parent f4095cc0cb
commit 6610b9c196
10 changed files with 217 additions and 16 deletions

View File

@@ -21,8 +21,23 @@ uv sync
**Run the application:**
Development mode (with auto-reload):
```bash
uv run python main.py
uv run run_dev.py
```
Production mode (with Gunicorn WSGI server):
```bash
# First install production dependencies
uv sync --extra prod
# Then run in production mode
uv run run_prod.py
```
Legacy mode (basic Dash server):
```bash
uv run main.py
```
The app will be available at http://127.0.0.1:8050

View File

@@ -21,11 +21,13 @@ COPY pyproject.toml uv.lock ./
# Copy source code (needed for editable install)
COPY src/ src/
COPY main.py .
COPY wsgi.py .
COPY run_prod.py .
COPY assets/ assets/
# Create virtual environment and install dependencies
# Create virtual environment and install dependencies (including production extras)
RUN uv venv .venv
RUN uv sync --frozen
RUN uv sync --frozen --extra prod
# Stage 2: Runtime
FROM python:3.11-slim as runtime
@@ -45,6 +47,8 @@ COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/src /app/src
COPY --from=builder /app/main.py /app/main.py
COPY --from=builder /app/assets /app/assets
COPY --from=builder /app/wsgi.py /app/wsgi.py
COPY --from=builder /app/run_prod.py /app/run_prod.py
# Make sure the virtual environment is in PATH
ENV PATH="/app/.venv/bin:$PATH"
@@ -55,7 +59,8 @@ ENV PYTHONPATH="/app/src:$PYTHONPATH"
# Environment variables for production
ENV EMBEDDINGBUDDY_HOST=0.0.0.0
ENV EMBEDDINGBUDDY_PORT=8050
ENV EMBEDDINGBUDDY_DEBUG=False
ENV EMBEDDINGBUDDY_DEBUG=false
ENV EMBEDDINGBUDDY_ENV=production
# Expose port
EXPOSE 8050
@@ -64,5 +69,5 @@ EXPOSE 8050
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:8050/', timeout=5)" || exit 1
# Run application
CMD ["python", "main.py"]
# Run application with Gunicorn in production
CMD ["python", "run_prod.py"]

View File

@@ -12,7 +12,7 @@ EmbeddingBuddy provides an intuitive web interface for analyzing high-dimensiona
embedding vectors by applying various dimensionality reduction algorithms and
visualizing the results in interactive 2D and 3D plots. The application features
a clean, modular architecture that makes it easy to test, maintain, and extend
with new features. It supports dual dataset visualization, allowing you to compare
with new features. It supports dual dataset visualization, allowing you to compare
documents and prompts to understand how queries relate to your content.
## Features
@@ -73,17 +73,77 @@ uv sync
2. **Run the application:**
**Development mode** (with auto-reload):
```bash
uv run python main.py
uv run run_dev.py
```
3. **Open your browser** to http://127.0.0.1:8050
**Production mode** (with Gunicorn WSGI server):
```bash
# Install production dependencies
uv sync --extra prod
# Run in production mode
uv run run_prod.py
```
**Legacy mode** (basic Dash server):
```bash
uv run main.py
```
3. **Open your browser** to <http://127.0.0.1:8050>
4. **Test with sample data**:
- Upload `sample_data.ndjson` (documents)
- Upload `sample_prompts.ndjson` (prompts) to see dual visualization
- Use the "Show prompts" toggle to compare how prompts relate to documents
## Docker
You can also run EmbeddingBuddy using Docker:
### Basic Usage
```bash
# Run in the background
docker compose up -d
```
The application will be available at <http://127.0.0.1:8050>
### With OpenSearch
To run with OpenSearch for enhanced search capabilities:
```bash
# Run in the background with OpenSearch
docker compose --profile opensearch up -d
```
This will start both the EmbeddingBuddy application and an OpenSearch instance.
OpenSearch will be available at <http://127.0.0.1:9200>
### Docker Commands
```bash
# Stop all services
docker compose down
# Stop and remove volumes
docker compose down -v
# View logs
docker compose logs embeddingbuddy
docker compose logs opensearch
# Rebuild containers
docker compose build
```
## Development
### Project Structure

View File

@@ -1,6 +1,6 @@
services:
opensearch:
image: opensearchproject/opensearch:2.13.0
image: opensearchproject/opensearch:2
container_name: embeddingbuddy-opensearch
profiles:
- opensearch

View File

@@ -12,7 +12,6 @@ dependencies = [
"scikit-learn>=1.3.2",
"dash-bootstrap-components>=1.5.0",
"umap-learn>=0.5.8",
"numba>=0.56.4",
"openTSNE>=1.0.0",
"mypy>=1.17.1",
"opensearch-py>=3.0.0",
@@ -32,11 +31,14 @@ security = [
"safety>=2.3.0",
"pip-audit>=2.6.0",
]
prod = [
"gunicorn>=21.2.0",
]
dev = [
"embeddingbuddy[test,lint,security]",
]
all = [
"embeddingbuddy[test,lint,security]",
"embeddingbuddy[test,lint,security,prod]",
]
[build-system]

26
run_dev.py Normal file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python3
"""
Development runner with auto-reload enabled.
This runs the Dash development server with hot reloading.
"""
import os
from src.embeddingbuddy.app import create_app, run_app
def main():
"""Run the application in development mode with auto-reload."""
# Force development settings
os.environ["EMBEDDINGBUDDY_ENV"] = "development"
os.environ["EMBEDDINGBUDDY_DEBUG"] = "true"
print("🚀 Starting EmbeddingBuddy in development mode...")
print("📁 Auto-reload enabled - changes will trigger restart")
print("🌐 Server will be available at http://127.0.0.1:8050")
print("⏹️ Press Ctrl+C to stop")
app = create_app()
# Run with development server (includes auto-reload when debug=True)
run_app(app, debug=True)
if __name__ == "__main__":
main()

49
run_prod.py Normal file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""
Production runner using Gunicorn WSGI server.
This provides better performance and stability for production deployments.
"""
import os
import subprocess
import sys
from src.embeddingbuddy.config.settings import AppSettings
def main():
"""Run the application in production mode with Gunicorn."""
# Force production settings
os.environ["EMBEDDINGBUDDY_ENV"] = "production"
os.environ["EMBEDDINGBUDDY_DEBUG"] = "false"
print("🚀 Starting EmbeddingBuddy in production mode...")
print(f"⚙️ Workers: {AppSettings.GUNICORN_WORKERS}")
print(f"🌐 Server will be available at http://{AppSettings.GUNICORN_BIND}")
print("⏹️ Press Ctrl+C to stop")
# Gunicorn command
cmd = [
"gunicorn",
"--workers", str(AppSettings.GUNICORN_WORKERS),
"--bind", AppSettings.GUNICORN_BIND,
"--timeout", str(AppSettings.GUNICORN_TIMEOUT),
"--keepalive", str(AppSettings.GUNICORN_KEEPALIVE),
"--access-logfile", "-",
"--error-logfile", "-",
"--log-level", "info",
"wsgi:application"
]
try:
subprocess.run(cmd, check=True)
except KeyboardInterrupt:
print("\n🛑 Shutting down...")
sys.exit(0)
except subprocess.CalledProcessError as e:
print(f"❌ Error running Gunicorn: {e}")
sys.exit(1)
except FileNotFoundError:
print("❌ Gunicorn not found. Install it with: uv add gunicorn")
print("💡 Or run in development mode with: python run_dev.py")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -72,6 +72,15 @@ class AppSettings:
DEBUG = os.getenv("EMBEDDINGBUDDY_DEBUG", "True").lower() == "true"
HOST = os.getenv("EMBEDDINGBUDDY_HOST", "127.0.0.1")
PORT = int(os.getenv("EMBEDDINGBUDDY_PORT", "8050"))
# Environment Configuration
ENVIRONMENT = os.getenv("EMBEDDINGBUDDY_ENV", "development") # development, production
# WSGI Server Configuration (for production)
GUNICORN_WORKERS = int(os.getenv("GUNICORN_WORKERS", "4"))
GUNICORN_BIND = os.getenv("GUNICORN_BIND", f"{HOST}:{PORT}")
GUNICORN_TIMEOUT = int(os.getenv("GUNICORN_TIMEOUT", "120"))
GUNICORN_KEEPALIVE = int(os.getenv("GUNICORN_KEEPALIVE", "5"))
# OpenSearch Configuration
OPENSEARCH_DEFAULT_SIZE = 100

23
uv.lock generated
View File

@@ -418,7 +418,6 @@ dependencies = [
{ name = "dash" },
{ name = "dash-bootstrap-components" },
{ name = "mypy" },
{ name = "numba" },
{ name = "numpy" },
{ name = "opensearch-py" },
{ name = "opentsne" },
@@ -431,6 +430,7 @@ dependencies = [
[package.optional-dependencies]
all = [
{ name = "bandit" },
{ name = "gunicorn" },
{ name = "mypy" },
{ name = "pip-audit" },
{ name = "pytest" },
@@ -451,6 +451,9 @@ lint = [
{ name = "mypy" },
{ name = "ruff" },
]
prod = [
{ name = "gunicorn" },
]
security = [
{ name = "bandit" },
{ name = "pip-audit" },
@@ -466,11 +469,11 @@ requires-dist = [
{ name = "bandit", extras = ["toml"], marker = "extra == 'security'", specifier = ">=1.7.5" },
{ name = "dash", specifier = ">=2.17.1" },
{ name = "dash-bootstrap-components", specifier = ">=1.5.0" },
{ name = "embeddingbuddy", extras = ["test", "lint", "security"], marker = "extra == 'all'" },
{ name = "embeddingbuddy", extras = ["test", "lint", "security"], marker = "extra == 'dev'" },
{ name = "embeddingbuddy", extras = ["test", "lint", "security", "prod"], marker = "extra == 'all'" },
{ name = "gunicorn", marker = "extra == 'prod'", specifier = ">=21.2.0" },
{ name = "mypy", specifier = ">=1.17.1" },
{ name = "mypy", marker = "extra == 'lint'", specifier = ">=1.5.0" },
{ name = "numba", specifier = ">=0.56.4" },
{ name = "numpy", specifier = ">=1.24.4" },
{ name = "opensearch-py", specifier = ">=3.0.0" },
{ name = "opentsne", specifier = ">=1.0.0" },
@@ -484,7 +487,7 @@ requires-dist = [
{ name = "scikit-learn", specifier = ">=1.3.2" },
{ name = "umap-learn", specifier = ">=0.5.8" },
]
provides-extras = ["test", "lint", "security", "dev", "all"]
provides-extras = ["test", "lint", "security", "prod", "dev", "all"]
[[package]]
name = "events"
@@ -520,6 +523,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305, upload-time = "2025-05-13T15:01:15.591Z" },
]
[[package]]
name = "gunicorn"
version = "23.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" },
]
[[package]]
name = "h11"
version = "0.16.0"

20
wsgi.py Normal file
View File

@@ -0,0 +1,20 @@
"""
WSGI entry point for production deployment.
Use this with a production WSGI server like Gunicorn.
"""
from src.embeddingbuddy.app import create_app
# Create the application instance
application = create_app()
# For compatibility with different WSGI servers
app = application
if __name__ == "__main__":
# This won't be used in production, but useful for testing
from src.embeddingbuddy.config.settings import AppSettings
application.run(
host=AppSettings.HOST,
port=AppSettings.PORT,
debug=False
)