restructure imports to work better with docker

This commit is contained in:
Micha R. Albert 2025-07-17 14:49:38 -04:00
parent 4a4d5fe4dd
commit 844e015dfa
Signed by: mra
SSH key fingerprint: SHA256:vjiZInsq3FRnDJk1YYWFhC/N62SAmVmY5H5wvViHhdg
14 changed files with 145 additions and 36 deletions

50
Dockerfile.dev Normal file
View file

@ -0,0 +1,50 @@
# Development Docker image with hot reload
FROM python:3.13-slim
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libc6-dev \
zlib1g \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user for development
RUN groupadd -r appuser && useradd -r -g appuser appuser
RUN mkdir -p /home/appuser
RUN chown -R appuser:appuser /home/appuser
# Set working directory
WORKDIR /app
COPY LICENSE README.md ./
# Install hatch for dependency management
RUN pip install --no-cache-dir hatch
# Copy pyproject.toml first for dependency caching
COPY pyproject.toml ./
# Generate and install dependencies
RUN hatch dep show requirements > requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Copy templates directory (needed at runtime)
COPY templates/ ./templates/
# Change ownership of the app directory to appuser
RUN chown -R appuser:appuser /app
# Switch to non-root user
USER appuser
# Set environment variables
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
# Expose port
EXPOSE 80
# The source code will be mounted as a volume
# Install in editable mode and run with auto-reload
CMD ["sh", "-c", "pip install -e . && uvicorn random_access.main:app --host 0.0.0.0 --port 80 --reload --reload-dir /app/src"]

20
docker-compose.dev.yml Normal file
View file

@ -0,0 +1,20 @@
# Development overrides for docker-compose
# Use with: docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
services:
# Override the api service to use the development configuration
api:
build:
context: .
dockerfile: Dockerfile.dev
env_file: .env
ports:
- "8000:80"
volumes:
# Mount source code for hot reload
- ./src:/app/src:ro
- ./pyproject.toml:/app/pyproject.toml:ro
# Mount templates if they change during development
- ./templates:/app/templates:ro
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/')"]

36
run.sh Executable file
View file

@ -0,0 +1,36 @@
#!/bin/bash
# Script to aid with running Docker containers for development and production environments
case "$1" in
"dev")
echo "🚀 Starting development environment with hot reload..."
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up --build
;;
"prod")
echo "🚀 Starting production environment..."
docker-compose up --build
;;
"stop")
echo "🛑 Stopping all services..."
docker-compose down
;;
"logs")
echo "📝 Showing logs..."
docker-compose logs -f
;;
"clean")
echo "🧹 Cleaning up containers and images..."
docker-compose down --rmi all --volumes
;;
*)
echo "Usage: $0 {dev|prod|stop|logs|clean}"
echo ""
echo "Commands:"
echo " dev - Start development environment with hot reload"
echo " prod - Start production environment"
echo " stop - Stop all services"
echo " logs - Show logs"
echo " clean - Clean up everything"
exit 1
;;
esac

View file

@ -4,4 +4,10 @@ __version__ = "0.0.1"
__author__ = "Micha R. Albert"
__email__ = "info@micha.zone"
__all__ = ["__version__", "__author__", "__email__"]
# Import the main app for easier access
try:
from .main import app
__all__ = ["__version__", "__author__", "__email__", "app"]
except ImportError:
# If imports fail (e.g., missing env vars), just export metadata
__all__ = ["__version__", "__author__", "__email__"]

View file

@ -8,8 +8,8 @@ from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from pyairtable.formulas import match
from database import get_session_by_token_cached
from settings import settings
from .database import get_session_by_token_cached
from .settings import settings
# Create HTTPBearer security scheme
security = HTTPBearer(

View file

@ -12,7 +12,7 @@ from aiocache.serializers import PickleSerializer
from pyairtable import Api as AirtableApi
from pyairtable.formulas import match
from settings import settings
from .settings import settings
logger = logging.getLogger("uvicorn.error")
@ -548,7 +548,7 @@ async def check_display_name_exists(display_name: str, users_table) -> bool:
async def cleanup_expired_sessions_worker(sessions_table):
"""Background worker to clean up expired sessions."""
# Import here to avoid circular imports (auth imports database)
from auth_utils import is_session_expired
from .auth_utils import is_session_expired
# Run cleanup immediately at startup
logger.info("Starting initial expired session cleanup at startup")

View file

@ -19,7 +19,7 @@ import logging.handlers
from pathlib import Path
from typing import Any, Dict
from settings import settings
from .settings import settings
class JSONFormatter(logging.Formatter):

View file

@ -16,21 +16,21 @@ from slowapi import Limiter
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from database import (
from .database import (
airtable_write_worker,
cleanup_expired_sessions_worker,
get_airtable_base,
get_table,
validate_all_schemas,
)
from logging_config import setup_logging, get_logger, log_request_context, log_performance_context
from routes.auth import create_auth_router
from routes.items import create_items_router, create_user_items_router
from routes.system import create_system_router
from routes.users import create_users_router
from security import SecurityHeaders, get_client_ip
from settings import settings
from slack_integration import create_slack_app, setup_slack_handlers
from .logging_config import setup_logging, get_logger, log_request_context, log_performance_context
from .routes.auth import create_auth_router
from .routes.items import create_items_router, create_user_items_router
from .routes.system import create_system_router
from .routes.users import create_users_router
from .security import SecurityHeaders, get_client_ip
from .settings import settings
from .slack_integration import create_slack_app, setup_slack_handlers
# Setup enhanced logging that integrates with uvicorn
setup_logging()

View file

@ -14,14 +14,13 @@ from fastapi.templating import Jinja2Templates
from pydantic import BaseModel, Field
from slowapi import Limiter
from auth_utils import (
from ..auth_utils import (
decode_oidc_state,
get_session_by_token,
hash_token,
is_session_expired,
)
from database import (
from ..database import (
check_display_name_exists,
create_session,
create_user,
@ -29,14 +28,14 @@ from database import (
get_user_record,
update_user_and_session,
)
from security import (
from ..security import (
create_safe_error_response,
generate_secure_token,
get_client_ip,
validate_airtable_id,
)
from settings import settings
from slack_integration import get_slack_user_id
from ..settings import settings
from ..slack_integration import get_slack_user_id
# Rate limiter for auth endpoints
limiter = Limiter(key_func=get_client_ip)

View file

@ -8,22 +8,20 @@ from fastapi.security import HTTPAuthorizationCredentials
from pydantic import BaseModel, Field, field_validator
from slowapi import Limiter
from auth_utils import extract_and_validate_auth, get_auth_credentials
from database import (
from ..auth_utils import extract_and_validate_auth, get_auth_credentials
from ..database import (
add_item_to_user,
get_all_items,
get_item_by_id,
)
from database import get_user_items as get_user_items_cached
from database import (
invalidate_user_items_cache,
get_user_items as get_user_items_cached,
)
from security import (
from ..security import (
create_safe_error_response,
get_client_ip,
validate_airtable_id,
)
from settings import settings
from ..settings import settings
# Rate limiter for item endpoints
limiter = Limiter(key_func=get_client_ip)

View file

@ -5,8 +5,8 @@ from pydantic import BaseModel, Field
from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler
from slowapi import Limiter
from security import get_client_ip
from settings import settings
from ..security import get_client_ip
from ..settings import settings
# Rate limiter for system endpoints
limiter = Limiter(key_func=get_client_ip)

View file

@ -5,9 +5,9 @@ from fastapi.security import HTTPAuthorizationCredentials
from pydantic import BaseModel, Field
from slowapi import Limiter
from auth_utils import extract_and_validate_auth, get_auth_credentials
from security import get_client_ip
from settings import settings
from ..auth_utils import extract_and_validate_auth, get_auth_credentials
from ..security import get_client_ip
from ..settings import settings
# Rate limiter for user endpoints
limiter = Limiter(key_func=get_client_ip)

View file

@ -6,7 +6,7 @@ from typing import Any
from fastapi import HTTPException, Request, status
from settings import settings
from .settings import settings
# Validation patterns
AIRTABLE_ID_PATTERN = re.compile(r"^rec[A-Za-z0-9]{14}$")

View file

@ -9,7 +9,7 @@ from zoneinfo import ZoneInfo
from slack_bolt.async_app import AsyncAck, AsyncApp, AsyncRespond, AsyncSay
from slack_bolt.response import BoltResponse
from database import (
from .database import (
get_all_games,
get_detailed_user_items_for_slack,
get_game_record,
@ -17,7 +17,7 @@ from database import (
get_user_by_slack_id,
get_user_sessions,
)
from settings import settings
from .settings import settings
def get_ordinal_suffix(day: int) -> str: