Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
27 KiB
Environment Detection System
Version: 1.0 Last Updated: November 2025 Audience: Development Team
Table of Contents
- Overview
- How It Works
- Using Environment Detection
- Configuration
- Development Guidelines
- Deployment Guide
- Testing
- Best Practices
Overview
The Orion platform uses automatic environment detection to determine the runtime environment (development, staging, or production) and adjust security settings accordingly.
Why Auto-Detection?
Instead of manually configuring environment settings in code, the system automatically detects the environment based on:
- Environment variables
- Runtime indicators (hostname, debug mode, etc.)
- Safe defaults for local development
This approach:
- ✅ Works out of the box in development (no configuration needed)
- ✅ Reduces configuration errors
- ✅ Follows "convention over configuration" principle
- ✅ Simplifies deployment process
Key Use Cases
The environment detection system is primarily used to determine:
- Cookie Security - Should cookies require HTTPS? (
secureflag) - Debug Mode - Enable detailed error messages in development
- CORS Settings - Stricter in production, relaxed in development
- Logging Level - Verbose in development, structured in production
How It Works
Detection Logic
The system determines the environment using a priority-based approach:
1. Check ENV environment variable
↓ (if not set)
2. Check ENVIRONMENT environment variable
↓ (if not set)
3. Auto-detect from system indicators
↓ (if no indicators)
4. Default to "development" (safe for local work)
Detection Details
Priority 1: ENV Variable
ENV=production
ENV=staging
ENV=development # or "dev" or "local"
Priority 2: ENVIRONMENT Variable
ENVIRONMENT=production
ENVIRONMENT=staging
ENVIRONMENT=development
Priority 3: Auto-Detection
The system checks for indicators:
Development Indicators:
DEBUG=trueenvironment variable- Hostname contains: "local", "dev", "laptop", "desktop"
- Running on: localhost, 127.0.0.1
Staging Indicators:
- Hostname contains: "staging", "stage"
Production Indicators:
- None of the above (explicit production setting recommended)
Priority 4: Safe Default
If no environment is detected: defaults to development
- Safe for local development
- Cookies work with HTTP
- Detailed error messages enabled
Using Environment Detection
Module Location
app/core/environment.py
Available Functions
from app.core.environment import (
get_environment, # Returns: "development" | "staging" | "production"
is_development, # Returns: bool
is_staging, # Returns: bool
is_production, # Returns: bool
should_use_secure_cookies, # Returns: bool
get_cached_environment, # Returns: cached environment (performance)
)
Common Usage Patterns
1. Cookie Security Settings
from fastapi import Response
from app.core.environment import should_use_secure_cookies
@router.post("/api/v1/admin/auth/login")
def login(response: Response):
# Set authentication cookie
response.set_cookie(
key="admin_token",
value=token,
httponly=True,
secure=should_use_secure_cookies(), # Auto-detects environment
samesite="lax",
path="/admin"
)
return {"message": "Logged in"}
Behavior:
- Development →
secure=False(works with HTTP) - Production →
secure=True(requires HTTPS)
2. Conditional Features
from app.core.environment import is_development, is_production
@router.get("/api/v1/debug/info")
def debug_info():
if not is_development():
raise HTTPException(status_code=404, detail="Not found")
# Only accessible in development
return {
"database": get_db_info(),
"cache": get_cache_stats(),
"config": get_config_dump()
}
3. Error Messages
from app.core.environment import is_development
@app.exception_handler(Exception)
async def exception_handler(request: Request, exc: Exception):
if is_development():
# Detailed error in development
return JSONResponse(
status_code=500,
content={
"error": str(exc),
"traceback": traceback.format_exc(),
"request": str(request.url)
}
)
else:
# Generic error in production
return JSONResponse(
status_code=500,
content={"error": "Internal server error"}
)
4. CORS Configuration
from fastapi.middleware.cors import CORSMiddleware
from app.core.environment import is_development
if is_development():
# Relaxed CORS for development
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
else:
# Strict CORS for production
app.add_middleware(
CORSMiddleware,
allow_origins=["https://yourdomain.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Content-Type", "Authorization"],
)
5. Logging Configuration
import logging
from app.core.environment import get_environment
def configure_logging():
env = get_environment()
if env == "development":
# Verbose logging for development
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
elif env == "staging":
# Moderate logging for staging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
else: # production
# Structured logging for production
logging.basicConfig(
level=logging.WARNING,
format='{"timestamp":"%(asctime)s","level":"%(levelname)s","message":"%(message)s"}'
)
6. Performance Optimization
from app.core.environment import get_cached_environment
def process_request(request: Request):
# Use cached version for frequent calls
env = get_cached_environment() # Cached - faster
# Instead of:
# env = get_environment() # Re-detects every time
if env == "production":
# Production-specific optimization
use_cache = True
else:
use_cache = False
Configuration
Development (Default)
No configuration needed! The system auto-detects development mode.
Indicators:
- Running on localhost or 127.0.0.1
- No environment variables set
- DEBUG mode enabled
Settings:
should_use_secure_cookies()→False- Cookies work with HTTP (http://localhost:8000)
- Detailed error messages
Staging Environment
Set environment variable:
# Option 1: ENV
export ENV=staging
# Option 2: ENVIRONMENT
export ENVIRONMENT=staging
# Start application
uvicorn main:app --host 0.0.0.0 --port 8000
Or in Docker:
ENV ENV=staging
Settings:
should_use_secure_cookies()→True- Requires HTTPS
- Moderate logging
Production Environment
Set environment variable:
# Option 1: ENV
export ENV=production
# Option 2: ENVIRONMENT
export ENVIRONMENT=production
# Start application
uvicorn main:app --host 0.0.0.0 --port 8000
Or in systemd service:
[Service]
Environment="ENV=production"
ExecStart=/usr/bin/uvicorn main:app --host 0.0.0.0 --port 8000
Or in Docker Compose:
services:
web:
image: orion:latest
environment:
- ENV=production
ports:
- "8000:8000"
Settings:
should_use_secure_cookies()→True- Requires HTTPS
- Minimal logging (warnings/errors only)
- Generic error messages
Development Guidelines
When Writing New Code
Use Environment Functions
DO:
from app.core.environment import should_use_secure_cookies, is_development
# Use the utility functions
if is_development():
enable_debug_toolbar()
response.set_cookie(secure=should_use_secure_cookies())
DON'T:
# Don't manually check environment strings
if os.getenv("ENV") == "production": # ❌ Bad
...
# Don't hardcode environment logic
response.set_cookie(secure=True) # ❌ Won't work in development
Keep Environment Logic Centralized
If you need custom environment-based behavior:
Option 1: Add to environment.py
# app/core/environment.py
def should_enable_caching() -> bool:
"""Determine if caching should be enabled."""
return not is_development()
def get_max_upload_size() -> int:
"""Get maximum upload size based on environment."""
env = get_environment()
if env == "development":
return 10 * 1024 * 1024 # 10MB in dev
elif env == "staging":
return 50 * 1024 * 1024 # 50MB in staging
else:
return 100 * 1024 * 1024 # 100MB in prod
Option 2: Use in your module
from app.core.environment import is_production
def get_cache_ttl() -> int:
"""Get cache TTL based on environment."""
return 3600 if is_production() else 60 # 1 hour in prod, 1 min in dev
Testing Environment Detection
Unit Tests
import os
import pytest
from app.core.environment import get_environment, should_use_secure_cookies
def test_environment_detection_with_env_var():
"""Test environment detection with ENV variable."""
os.environ["ENV"] = "production"
# Clear cache first
import app.core.environment as env_module
env_module._cached_environment = None
assert get_environment() == "production"
assert should_use_secure_cookies() == True
# Cleanup
del os.environ["ENV"]
def test_environment_defaults_to_development():
"""Test that environment defaults to development."""
# Ensure no env vars set
os.environ.pop("ENV", None)
os.environ.pop("ENVIRONMENT", None)
# Clear cache
import app.core.environment as env_module
env_module._cached_environment = None
assert get_environment() == "development"
assert should_use_secure_cookies() == False
Integration Tests
def test_login_sets_secure_cookie_in_production(test_client, monkeypatch):
"""Test that login sets secure cookie in production."""
# Mock production environment
monkeypatch.setenv("ENV", "production")
# Clear cache
import app.core.environment as env_module
env_module._cached_environment = None
# Test login
response = test_client.post("/api/v1/admin/auth/login", json={
"username": "admin",
"password": "admin123"
})
# Check cookie has secure flag
set_cookie_header = response.headers.get("set-cookie")
assert "Secure" in set_cookie_header
Debugging Environment Detection
Print Current Environment
# Add to your startup or debug endpoint
from app.core.environment import get_environment, should_use_secure_cookies
@app.on_event("startup")
async def startup_event():
env = get_environment()
secure = should_use_secure_cookies()
print(f"🌍 Environment: {env}")
print(f"🔒 Secure cookies: {secure}")
print(f"📍 Running on: {os.getenv('HOSTNAME', 'localhost')}")
Debug Endpoint (Development Only)
from app.core.environment import get_environment, is_development
import os
@router.get("/debug/environment")
def debug_environment():
"""Debug endpoint to check environment detection."""
if not is_development():
raise HTTPException(status_code=404)
return {
"detected_environment": get_environment(),
"should_use_secure_cookies": should_use_secure_cookies(),
"env_var_ENV": os.getenv("ENV"),
"env_var_ENVIRONMENT": os.getenv("ENVIRONMENT"),
"env_var_DEBUG": os.getenv("DEBUG"),
"hostname": os.getenv("HOSTNAME", "unknown"),
}
Deployment Guide
Local Development
Setup:
# Clone repo
git clone <orion-repo>
cd orion-repo
# Create virtual environment
python -m venv venv
source venv/bin/activate # or `venv\Scripts\activate` on Windows
# Install dependencies
pip install -r requirements.txt
# Run application (no environment config needed!)
uvicorn main:app --reload
Access:
- Admin: http://localhost:8000/admin
- Store: http://localhost:8000/store/{code}
- Shop: http://localhost:8000/shop
Environment:
- Auto-detected as "development"
- Cookies work with HTTP
- Debug mode enabled
Staging Deployment
Docker Compose:
version: '3.8'
services:
web:
build: .
environment:
- ENV=staging
- DATABASE_URL=postgresql://user:pass@db:5432/orion_staging
ports:
- "8000:8000"
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_DB=orion_staging
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
Kubernetes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: orion-staging
spec:
template:
spec:
containers:
- name: web
image: orion:latest
env:
- name: ENV
value: "staging"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
Requirements:
- HTTPS enabled (nginx/traefik)
- SSL certificates configured
- Environment variable set:
ENV=staging
Production Deployment
systemd Service:
[Unit]
Description=Orion Platform
After=network.target
[Service]
User=orion
WorkingDirectory=/opt/orion
Environment="ENV=production"
Environment="DATABASE_URL=postgresql://user:pass@localhost/orion_prod"
ExecStart=/opt/orion/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
Restart=always
[Install]
WantedBy=multi-user.target
Docker:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV ENV=production
ENV PYTHONUNBUFFERED=1
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
Requirements:
- ✅ HTTPS enabled and enforced
- ✅ SSL certificates valid
- ✅ Environment variable:
ENV=production - ✅ Database backups configured
- ✅ Monitoring and logging enabled
- ✅ Security headers configured
Environment Variable Checklist
| Environment | ENV Variable | HTTPS Required | Debug Mode | Cookie Secure |
|---|---|---|---|---|
| Development | Not set (or "development") | ❌ No | ✅ Yes | ❌ No |
| Staging | "staging" | ✅ Yes | ⚠️ Limited | ✅ Yes |
| Production | "production" | ✅ Yes | ❌ No | ✅ Yes |
Testing
Manual Testing
Test Environment Detection
# Test development (default)
python -c "
from app.core.environment import get_environment, should_use_secure_cookies
print(f'Environment: {get_environment()}')
print(f'Secure cookies: {should_use_secure_cookies()}')
"
# Expected output:
# Environment: development
# Secure cookies: False
# Test production
ENV=production python -c "
from app.core.environment import get_environment, should_use_secure_cookies
print(f'Environment: {get_environment()}')
print(f'Secure cookies: {should_use_secure_cookies()}')
"
# Expected output:
# Environment: production
# Secure cookies: True
Test Cookie Behavior
Development:
# Start server (development mode)
uvicorn main:app --reload
# Login and check cookie
curl -X POST http://localhost:8000/api/v1/admin/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}' \
-v 2>&1 | grep -i "set-cookie"
# Should see: Set-Cookie: admin_token=...; HttpOnly; SameSite=Lax; Path=/admin
# Should NOT see: Secure flag
Production:
# Start server (production mode)
ENV=production uvicorn main:app
# Login and check cookie
curl -X POST https://yourdomain.com/api/v1/admin/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}' \
-v 2>&1 | grep -i "set-cookie"
# Should see: Set-Cookie: admin_token=...; Secure; HttpOnly; SameSite=Lax; Path=/admin
# Should see: Secure flag present
Automated Tests
pytest Examples
# tests/test_environment.py
import pytest
import os
from app.core.environment import (
get_environment,
is_development,
is_staging,
is_production,
should_use_secure_cookies
)
@pytest.fixture(autouse=True)
def clear_environment_cache():
"""Clear cached environment before each test."""
import app.core.environment as env_module
env_module._cached_environment = None
yield
env_module._cached_environment = None
class TestEnvironmentDetection:
def test_default_is_development(self):
"""Test that default environment is development."""
# Clear any env vars
os.environ.pop("ENV", None)
os.environ.pop("ENVIRONMENT", None)
assert get_environment() == "development"
assert is_development() == True
assert is_production() == False
assert should_use_secure_cookies() == False
def test_env_variable_production(self, monkeypatch):
"""Test ENV=production detection."""
monkeypatch.setenv("ENV", "production")
assert get_environment() == "production"
assert is_production() == True
assert should_use_secure_cookies() == True
def test_environment_variable_staging(self, monkeypatch):
"""Test ENVIRONMENT=staging detection."""
monkeypatch.setenv("ENVIRONMENT", "staging")
assert get_environment() == "staging"
assert is_staging() == True
assert should_use_secure_cookies() == True
def test_env_takes_priority_over_environment(self, monkeypatch):
"""Test that ENV variable has priority."""
monkeypatch.setenv("ENV", "production")
monkeypatch.setenv("ENVIRONMENT", "staging")
# ENV should win
assert get_environment() == "production"
def test_debug_true_is_development(self, monkeypatch):
"""Test that DEBUG=true results in development."""
monkeypatch.delenv("ENV", raising=False)
monkeypatch.delenv("ENVIRONMENT", raising=False)
monkeypatch.setenv("DEBUG", "true")
assert get_environment() == "development"
class TestCookieSecurity:
def test_secure_cookies_in_production(self, monkeypatch):
"""Test cookies are secure in production."""
monkeypatch.setenv("ENV", "production")
assert should_use_secure_cookies() == True
def test_insecure_cookies_in_development(self):
"""Test cookies are not secure in development."""
os.environ.pop("ENV", None)
assert should_use_secure_cookies() == False
# tests/test_authentication_cookies.py
def test_login_cookie_matches_environment(test_client, monkeypatch):
"""Test that login cookie security matches environment."""
# Test development
monkeypatch.delenv("ENV", raising=False)
response = test_client.post("/api/v1/admin/auth/login", json={
"username": "admin",
"password": "admin123"
})
set_cookie = response.headers.get("set-cookie", "")
assert "Secure" not in set_cookie # No Secure in development
# Test production
monkeypatch.setenv("ENV", "production")
# Clear cache
import app.core.environment as env_module
env_module._cached_environment = None
response = test_client.post("/api/v1/admin/auth/login", json={
"username": "admin",
"password": "admin123"
})
set_cookie = response.headers.get("set-cookie", "")
assert "Secure" in set_cookie # Secure in production
Best Practices
DO ✅
-
Use the environment utility functions:
from app.core.environment import should_use_secure_cookies response.set_cookie(secure=should_use_secure_cookies()) -
Set ENV in production/staging:
export ENV=production -
Use cached version for frequent calls:
from app.core.environment import get_cached_environment env = get_cached_environment() # Cached - faster -
Keep environment logic in environment.py:
- Add new environment-dependent functions to
environment.py - Don't scatter environment checks throughout codebase
- Add new environment-dependent functions to
-
Document environment-dependent behavior:
def send_email(to: str, subject: str): """ Send email to recipient. Environment behavior: - Development: Logs email to console (no actual send) - Staging: Sends to test email address - Production: Sends to actual recipient """ if is_development(): logger.info(f"[DEV] Email to {to}: {subject}") elif is_staging(): actual_to = f"staging+{to}@example.com" send_via_smtp(actual_to, subject) else: send_via_smtp(to, subject) -
Test with different environments:
@pytest.mark.parametrize("env", ["development", "staging", "production"]) def test_feature_in_all_environments(env, monkeypatch): monkeypatch.setenv("ENV", env) # Clear cache import app.core.environment as env_module env_module._cached_environment = None # Test your feature
DON'T ❌
-
Don't hardcode environment checks:
# ❌ Bad if os.getenv("ENV") == "production": use_https = True # ✅ Good use_https = should_use_secure_cookies() -
Don't assume environment:
# ❌ Bad - assumes production response.set_cookie(secure=True) # ✅ Good - adapts to environment response.set_cookie(secure=should_use_secure_cookies()) -
Don't bypass environment detection:
# ❌ Bad FORCE_PRODUCTION = True if FORCE_PRODUCTION: ... # ✅ Good - use environment variable # export ENV=production if is_production(): ... -
Don't mix environment checking methods:
# ❌ Bad - inconsistent if os.getenv("ENV") == "prod": log_level = "WARNING" elif is_development(): log_level = "DEBUG" # ✅ Good - consistent env = get_environment() if env == "production": log_level = "WARNING" elif env == "development": log_level = "DEBUG" -
Don't forget to clear cache in tests:
# ❌ Bad - cache may interfere def test_production(): os.environ["ENV"] = "production" assert get_environment() == "production" # May fail if cached # ✅ Good - clear cache def test_production(): import app.core.environment as env_module env_module._cached_environment = None os.environ["ENV"] = "production" assert get_environment() == "production"
Security Considerations
-
Always use HTTPS in production:
- Environment detection enables
secure=Truefor cookies - But you must configure HTTPS at the server level
- Environment detection enables
-
Never disable security in production:
# ❌ NEVER do this if is_production(): response.set_cookie(secure=False) # Security vulnerability! -
Validate environment variable values:
- The system only accepts: "development", "staging", "production"
- Invalid values default to "development" (safe)
-
Monitor environment in production:
@app.on_event("startup") async def check_production_settings(): if is_production(): # Verify production requirements assert os.getenv("DATABASE_URL"), "DATABASE_URL required in production" assert os.getenv("SECRET_KEY"), "SECRET_KEY required in production" logger.info("✅ Production environment verified")
Troubleshooting
Issue: Cookies Not Working
Symptom: Authentication cookies not being sent/received
Check:
-
Environment detection:
from app.core.environment import get_environment, should_use_secure_cookies print(f"Environment: {get_environment()}") print(f"Secure cookies: {should_use_secure_cookies()}") -
Development (HTTP):
should_use_secure_cookies()should returnFalse- Access via
http://localhost:8000(not HTTPS)
-
Production (HTTPS):
should_use_secure_cookies()should returnTrue- Access via
https://yourdomain.com(with HTTPS) - Verify SSL certificate is valid
Issue: Wrong Environment Detected
Symptom: System detects wrong environment
Solution:
# Explicitly set environment
export ENV=production
# Restart application
uvicorn main:app --reload
# Verify
python -c "from app.core.environment import get_environment; print(get_environment())"
Issue: Environment Not Changing
Symptom: Environment stuck on old value
Cause: Environment is cached
Solution:
# Clear cache and re-detect
import app.core.environment as env_module
env_module._cached_environment = None
# Now check again
from app.core.environment import get_environment
print(get_environment())
Issue: Tests Failing Due to Environment
Symptom: Tests pass locally but fail in CI/CD
Solution: Always clear cache in test fixtures:
@pytest.fixture(autouse=True)
def clear_environment_cache():
"""Clear environment cache before each test."""
import app.core.environment as env_module
env_module._cached_environment = None
yield
env_module._cached_environment = None
Summary
Quick Reference
# Import
from app.core.environment import (
get_environment,
is_development,
is_production,
should_use_secure_cookies
)
# Usage
env = get_environment() # "development" | "staging" | "production"
if is_development():
# Development-only code
pass
if is_production():
# Production-only code
pass
# Cookie security (most common use case)
response.set_cookie(secure=should_use_secure_cookies())
Configuration
| Environment | Set Variable | Cookie Secure | HTTPS Required |
|---|---|---|---|
| Development | Not needed (default) | False | No |
| Staging | ENV=staging |
True | Yes |
| Production | ENV=production |
True | Yes |
Key Points
- ✅ Development works out of the box - No configuration needed
- ✅ Production requires explicit setting - Set
ENV=production - ✅ Use utility functions - Don't check environment directly
- ✅ HTTPS required in production - Configure at server level
- ✅ Test with different environments - Use monkeypatch in tests
Additional Resources
Related Documentation
External References
Questions? Contact the backend team or check the main documentation.
End of Document