This guide covers installing and configuring fact_inventory for standalone operation. For deployment patterns (bare metal, Kubernetes, embedding), see DEPLOYMENT.md. For development setup, see DEVELOPMENT.md.
- Python 3.12+
- PostgreSQL 16+ (recommended for JSONB and GIN index performance)
- uv (Python package installer; see https://docs.astral.sh/uv/)
pyproject.toml keeps a record of the required python modules.
- Clone the repository:
git clone <repository-url>
cd python-fact_inventory- Install dependencies:
NOTE: For a fully isolated python:
export UV_PYTHON_INSTALL_DIR="$(pwd)/.python3"
uv python install 3
uv venv --python 3 --seed --link-mode copy --prompt fact_inventory
echo "installing dependencies"
source .venv/bin/activate
uv sync --link-mode=copyNOTE: For use of a homedir uv cache
uv sync- Set up environment configuration:
Create a .env.production file (or .env.{DEPLOYMENT} for your target environment):
DATABASE_URI=postgresql+asyncpg://user:password@localhost/dbname- Start the application:
export WEB_CONCURRENCY=8
DEPLOYMENT=XYZ uvicorn fact_inventory:app_factory --factory --host 0.0.0.0 --port 8000The application is now listening on http://0.0.0.0:8000/api/v1/facts with 8 workers.
Configuration is managed through environment variables. The DEPLOYMENT variable selects which .env file to load and sets the deployment_environment field:
export DEPLOYMENT=production # loads .env.production, sets deployment_environment="production"
export DEPLOYMENT=staging # loads .env.staging, sets deployment_environment="staging"
export DEPLOYMENT=testing # loads .env.testing, sets deployment_environment="testing"All available configuration options are defined in ../fact_inventory/lib/settings.py.
Common settings:
| Variable | Description |
|---|---|
DEPLOYMENT |
Environment name (e.g., production, staging, testing) |
DATABASE_URI |
PostgreSQL connection string: postgresql+asyncpg://user:pass@host/db |
DEBUG |
Enable debug mode and OpenAPI documentation |
RETENTION_DAYS |
Keep facts for this many days before auto-purge |
HISTORY_MAX_ENTRIES |
Keep this many newest facts per client after deduplication |
PostgreSQL 16+ is recommended for JSONB and GIN index performance. For development, SQLite is supported but not recommended for production.
PostgreSQL connection string format:
postgresql+asyncpg://username:password@hostname:port/database_name
Example with local PostgreSQL:
export DATABASE_URI="postgresql+asyncpg://fact_user:secure_password@localhost:5432/fact_inventory"Rate limiting is configured with two environment variables. The default is 2 requests per hour per IP address. Rate limiting is based on the client's IP address and can be bypassed with IP rotation.
export API_RATE_LIMIT_MAX_REQUESTS=10
export API_RATE_LIMIT_UNIT=hourIf clients submit large payloads:
# Increase total request body limit to 50 MB
export MAX_REQUEST_BODY_MB=50
# Increase per-field limit to 10 MB
export MAX_JSON_FIELD_MB=10
# Ensure total > 4 x per-field (there are 4 JSON fact fields)
# Example: 50 > 4 * 10 (40) -- OKPurge old facts automatically:
# Keep facts for 180 days (6 months)
export RETENTION_DAYS=180
# Check for expired facts every 24 hours
export RETENTION_CHECK_INTERVAL_HOURS=24
# Add up to 30 minutes of random jitter to prevent thundering-herd
export RETENTION_CHECK_JITTER_MINUTES=30Keep only the newest facts per client:
# Keep the 5 newest facts per client
export HISTORY_MAX_ENTRIES=5
# Check for duplicates every 12 hours
export HISTORY_CHECK_INTERVAL_HOURS=12
# Add up to 15 minutes of random jitter
export HISTORY_CHECK_JITTER_MINUTES=15For production, use Litestar's Alembic migrations to manage schema changes safely across deployments.
Why use Alembic?
- Version control for database schema
- Safe rollback if migrations fail
- Audit trail of schema changes
- Can be scripted into CI/CD pipelines
- Works across multiple instances without race conditions
Setup:
- Run migrations against your production database:
DEPLOYMENT=production uv run litestar --app fact_inventory:app database upgrade- Start the application (tables will already exist):
DEPLOYMENT=production uvicorn fact_inventory:app_factory --factory --host 0.0.0.0 --port 8000Start the ASGI server:
DEPLOYMENT=production uvicorn fact_inventory:app_factory --factory --host 0.0.0.0 --port 8000The application listens on http://0.0.0.0:8000.
Important deployment note: By default, APP_PREFIX=/ because a reverse proxy (nginx, Apache, or Kubernetes Ingress) strips the external /fact_inventory prefix before forwarding. The application internally sees clean paths like /api/v1/facts.
For direct development access without a reverse proxy, override the prefix:
APP_PREFIX=/fact_inventory uvicorn fact_inventory:app_factory --factory --host 0.0.0.0 --port 8000Then access the API at http://localhost:8000/fact_inventory/api/v1/facts.
Create /etc/systemd/system/fact-inventory.service:
[Unit]
Description=Fact Inventory Service
After=network.target postgresql.service
Wants=postgresql.service
[Service]
Type=notify
User=fact_inventory
Group=fact_inventory
WorkingDirectory=/opt/fact_inventory
Environment="DEPLOYMENT=production"
ExecStart=/opt/fact_inventory/.venv/bin/uvicorn fact_inventory:app_factory --factory --host 127.0.0.1 --port 8000
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable fact-inventory
sudo systemctl start fact-inventoryView logs:
sudo journalctl -u fact-inventory -fEnable OpenAPI documentation:
DEPLOYMENT=testing DEBUG=true uvicorn fact_inventory:app_factory --factoryThen visit:
- OpenAPI spec:
http://localhost:8000/fact_inventory/schema - Swagger UI:
http://localhost:8000/fact_inventory/schema/swagger
(Replace /fact_inventory with your configured APP_PREFIX if different.)
The application provides health and readiness endpoints for monitoring. Both are disabled by default; enable them with environment variables:
export ENABLE_HEALTH_ENDPOINT=true
export ENABLE_READY_ENDPOINT=true- Health:
GET /health(orGET /fact_inventory/healthif usingAPP_PREFIX=/fact_inventory) - Ready:
GET /ready(orGET /fact_inventory/readyif usingAPP_PREFIX=/fact_inventory)
Example with curl:
DEPLOYMENT=testing ENABLE_HEALTH_ENDPOINT=true uvicorn fact_inventory:app_factory --factory &
curl http://localhost:8000/healthThese are useful for container orchestration probes and load balancer health checks.
Verify PostgreSQL is running and the connection string is correct:
psql "postgresql://fact_user@localhost/fact_inventory"If that works, verify the async connection string with the correct driver:
export DATABASE_URI="postgresql+asyncpg://fact_user@localhost/fact_inventory"Two possible causes:
-
Total request body exceeds
MAX_REQUEST_BODY_MB- Increase
MAX_REQUEST_BODY_MB - Ensure
MAX_REQUEST_BODY_MB > 4 x MAX_JSON_FIELD_MB
- Increase
-
Single JSON field exceeds
MAX_JSON_FIELD_MB- Increase
MAX_JSON_FIELD_MB - Check logs for the specific field name
- Increase
Example fix:
export MAX_REQUEST_BODY_MB=50
export MAX_JSON_FIELD_MB=10Clients are rate-limited by IP by default.
- Adjust
API_RATE_LIMIT_MAX_REQUESTSandAPI_RATE_LIMIT_UNIT - Note: Rate limits can be bypassed with IP rotation
Check environment variables:
echo $DEPLOYMENT
echo $DATABASE_URIEnsure .env.{DEPLOYMENT} exists in the application directory and contains DATABASE_URI.
The background cleanup tasks (retention and history pruning) run after startup on their configured intervals with random jitter. They do not run during startup. If you see periodic spikes in database load that impact performance, increase the jitter:
export RETENTION_CHECK_JITTER_MINUTES=60
export HISTORY_CHECK_JITTER_MINUTES=60Or increase the check intervals to run less frequently:
export RETENTION_CHECK_INTERVAL_HOURS=48
export HISTORY_CHECK_INTERVAL_HOURS=24- Deployment: See DEPLOYMENT.md for bare metal, Kubernetes, and embedding patterns
- Querying: See VIEWS.md for SQL examples and pre-built views