{"record":{"id":"f8f7d157601bdeba","repo":"jamiepine/voicebox","slug":"profile-profile-id-has-no-samples","errorCode":null,"errorMessage":"Profile {profile_id} has no samples","messagePattern":"Profile (.+?) has no samples","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"backend/services/export_import.py","lineNumber":67,"sourceCode":"    Args:\n        profile_id: Profile ID to export\n        db: Database session\n        \n    Returns:\n        ZIP file contents as bytes\n        \n    Raises:\n        ValueError: If profile not found or has no samples\n    \"\"\"\n    # Get profile\n    profile = db.query(DBVoiceProfile).filter_by(id=profile_id).first()\n    if not profile:\n        raise ValueError(f\"Profile {profile_id} not found\")\n    \n    # Get all samples\n    samples = db.query(DBProfileSample).filter_by(profile_id=profile_id).all()\n    if not samples:\n        raise ValueError(f\"Profile {profile_id} has no samples\")\n    \n    # Create ZIP in memory\n    zip_buffer = io.BytesIO()\n    \n    with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:\n        # Check if profile has avatar\n        has_avatar = False\n        if profile.avatar_path:\n            avatar_path = config.resolve_storage_path(profile.avatar_path)\n            if avatar_path is not None and avatar_path.exists():\n                has_avatar = True\n                # Add avatar to ZIP root with original extension\n                avatar_ext = avatar_path.suffix\n                zip_file.write(avatar_path, f\"avatar{avatar_ext}\")\n\n        # Create manifest.json\n        manifest = {\n            \"version\": \"1.0\",","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/export_import.py#L49-L85","documentation":"After finding the profile, export_profile_to_zip() queries DBProfileSample by profile_id; an empty result raises ValueError. A profile with zero samples has no audio to archive, so the export is rejected up front rather than producing an empty zip.","triggerScenarios":"Exporting a freshly-created profile before any samples are recorded/uploaded; a profile whose samples were all deleted but whose row remains.","commonSituations":"User clicks Export too early; empty profile object left in the list; samples removed via a separate flow.","solutions":["Record or upload at least one sample before exporting.","Disable Export in the UI until the profile has at least one sample.","Surface this ValueError as 400/409 with a clear 'add samples first' message."],"exampleFix":"# before — export immediately after create\ndata = export_profile_to_zip(new_profile_id, db)\n\n# after — only export once samples exist\nif db.query(DBProfileSample).filter_by(profile_id=pid).count() == 0:\n    return None\ndata = export_profile_to_zip(pid, db)","handlingStrategy":"validation","validationCode":"from backend.database import ProfileSample as DBProfileSample\n\ndef has_samples(profile_id, db) -> bool:\n    return db.query(DBProfileSample).filter_by(profile_id=profile_id).count() > 0","typeGuard":null,"tryCatchPattern":"try:\n    data = export_profile_to_zip(pid, db)\nexcept ValueError as e:\n    if \"has no samples\" in str(e):\n        # prompt user to add at least one sample\n        ...\n    raise","preventionTips":["Show sample count in the profile list; disable Export when it's 0.","Validate before issuing the export request rather than after."],"tags":["profiles","export","empty-state","validation"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}