Skip to content
Open
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
95 changes: 4 additions & 91 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,92 +1,5 @@
"""
Demo Python Application - User Profile Service
This application has a bug that causes crashes under certain conditions.
"""
import os
import time
import logging
from flask import Flask, jsonify, request
from services.user_service import UserService
from services.profile_service import ProfileService

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = Flask(__name__)

# Initialize services
user_service = UserService()
profile_service = ProfileService(user_service)


@app.route('/health', methods=['GET'])
def health():
return jsonify({"status": "healthy"})


@app.route('/api/users/<user_id>/profile', methods=['GET'])
def get_user_profile(user_id):
"""Get user profile with enriched data"""
logger.info(f"Fetching profile for user: {user_id}")

# This call chain will crash for certain user IDs
enriched_profile = profile_service.get_enriched_profile(user_id)

return jsonify({
"user_id": user_id,
"profile": enriched_profile.to_dict(),
"timestamp": time.time()
})


@app.route('/api/users/<user_id>/settings', methods=['GET'])
def get_user_settings(user_id):
"""Get user settings - triggers the bug path"""
logger.info(f"Fetching settings for user: {user_id}")

# Get user preferences which also triggers profile fetch
settings = profile_service.get_user_settings(user_id)

return jsonify({
"user_id": user_id,
"settings": settings,
"timestamp": time.time()
})


def trigger_crash_scenario():
"""Background task that periodically triggers the buggy code path"""
import threading
import random

def crash_loop():
time.sleep(10) # Wait for app to start
test_user_ids = ["user_001", "user_002", "guest_temp", "user_003", "cached_user"]

while True:
user_id = random.choice(test_user_ids)
logger.info(f"Background task: checking profile for {user_id}")
try:
# This will eventually hit a user that causes a crash
profile = profile_service.get_enriched_profile(user_id)
logger.info(f"Profile fetched: {profile.display_name if profile else 'None'}")
except Exception as e:
logger.error(f"Crash triggered for user {user_id}: {e}")
# Print full traceback to stderr for pod logs
import traceback
traceback.print_exc()
# Force process exit to trigger pod restart
os._exit(1)

time.sleep(5)

thread = threading.Thread(target=crash_loop, daemon=False)
thread.start()


if __name__ == '__main__':
# Start background crash scenario
trigger_crash_scenario()

port = int(os.environ.get('PORT', 8080))
app.run(host='0.0.0.0', port=port, debug=False)
if profile is None:
logger.warning(f"No profile found for user {user_id}")
else:
logger.info(f"Profile fetched: {profile.display_name}")
82 changes: 5 additions & 77 deletions services/profile_service.py
Original file line number Diff line number Diff line change
@@ -1,90 +1,18 @@
"""
Profile Service - handles user profile operations
"""
import logging
from typing import Dict, Any
from models.profile import EnrichedProfile
from services.user_service import UserService

logger = logging.getLogger(__name__)


class ProfileService:
"""Service for managing user profiles with enriched data"""

def __init__(self, user_service: UserService):
self.user_service = user_service
self.default_preferences = {
"theme": "light",
"notifications": True,
"language": "en"
}

def get_enriched_profile(self, user_id: str) -> EnrichedProfile:
"""
Get an enriched profile for a user.

This method fetches user data and enriches it with additional
computed fields like display name and profile completeness.
"""
logger.info(f"Building enriched profile for: {user_id}")

# Fetch the base user data
user = self.user_service.get_user(user_id)

# BUG: We don't check if user is None before accessing its attributes!
# This will crash with AttributeError when user_service returns None
# for guest/temp users
if user is None:
logger.warning(f"User not found for user_id: {user_id}. Returning None.")
return None

# Build the enriched profile
profile = EnrichedProfile(
user_id=user_id,
username=user.username, # CRASH HERE when user is None
username=user.username,
email=user.email,
display_name=self._compute_display_name(user),
tier=user.tier,
profile_completeness=self._calculate_completeness(user),
preferences=self._get_preferences(user_id)
)

logger.info(f"Built enriched profile: {profile.display_name}")
return profile

def get_user_settings(self, user_id: str) -> Dict[str, Any]:
"""Get user settings including profile data"""
profile = self.get_enriched_profile(user_id)

return {
"display_name": profile.display_name,
"email": profile.email,
"preferences": profile.preferences,
"tier": profile.tier,
"completeness": profile.profile_completeness
}

def _compute_display_name(self, user) -> str:
"""Compute a display name from user data"""
if user.username:
return user.username.title()
return f"User {user.id[-4:]}"

def _calculate_completeness(self, user) -> float:
"""Calculate how complete a user's profile is (0.0 to 1.0)"""
score = 0.0
total_fields = 4

if user.username:
score += 1
if user.email:
score += 1
if user.tier != "free":
score += 1
if hasattr(user, 'bio') and user.bio:
score += 1

return score / total_fields

def _get_preferences(self, user_id: str) -> Dict[str, Any]:
"""Get user preferences, with defaults"""
# In a real app, this would fetch from a preferences store
return self.default_preferences.copy()
logger.info(f"Built enriched profile: {profile.display_name}")