File size: 7,443 Bytes
c63c0e5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
"""Supabase storage helper for uploading files.
This module provides functions to upload files to Supabase storage
using HTTP requests, avoiding the dependency issues with the supabase client library.
"""
import os
import requests
import logging
from typing import Optional, Dict, Any
from pathlib import Path
# Import Supabase credentials from environment variables
from dotenv import load_dotenv
load_dotenv()
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
if not SUPABASE_URL or not SUPABASE_KEY:
raise ImportError("Could not load Supabase credentials from environment variables")
logger = logging.getLogger(__name__)
# Supabase storage API configuration
STORAGE_BASE_URL = f"{SUPABASE_URL}/storage/v1"
HEADERS = {
"apikey": SUPABASE_KEY,
"Authorization": f"Bearer {SUPABASE_KEY}",
"Content-Type": "application/json"
}
def upload_file_to_bucket(
file_path: str,
bucket_name: str = "images",
storage_path: Optional[str] = None,
file_options: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Upload a file to Supabase storage bucket.
Args:
file_path: Local path to the file to upload
bucket_name: Name of the storage bucket (default: "images")
storage_path: Path in the bucket where to store the file (default: filename)
file_options: Optional file options like cache-control, upsert, etc.
Returns:
Dictionary with upload result information
"""
try:
# Check if file exists
if not os.path.exists(file_path):
raise FileNotFoundError(f"File not found: {file_path}")
# Get file info
file_path_obj = Path(file_path)
file_name = file_path_obj.name
# Use provided storage_path or default to filename
if storage_path is None:
storage_path = file_name
# Prepare upload URL
upload_url = f"{STORAGE_BASE_URL}/object/{bucket_name}/{storage_path}"
# Prepare headers for file upload
upload_headers = {
"apikey": SUPABASE_KEY,
"Authorization": f"Bearer {SUPABASE_KEY}"
}
# Add file options if provided
if file_options:
for key, value in file_options.items():
upload_headers[f"x-upsert"] = str(value).lower() if key == "upsert" else str(value)
# Read and upload file
with open(file_path, "rb") as f:
response = requests.post(
upload_url,
headers=upload_headers,
data=f
)
if response.status_code == 200:
result = response.json()
logger.info(f"✅ File uploaded successfully: {file_name}")
logger.info(f"📁 Storage path: {storage_path}")
logger.info(f"🔗 Public URL: {result.get('publicUrl', 'N/A')}")
return {
"success": True,
"file_name": file_name,
"storage_path": storage_path,
"public_url": result.get('publicUrl'),
"response": result
}
else:
logger.error(f"❌ Upload failed with status {response.status_code}")
logger.error(f"Response: {response.text}")
return {
"success": False,
"error": f"Upload failed: {response.status_code}",
"response": response.text
}
except Exception as e:
logger.error(f"❌ Error uploading file: {e}")
return {
"success": False,
"error": str(e)
}
def get_file_url(bucket_name: str, file_path: str) -> str:
"""
Get the public URL for a file in Supabase storage.
Args:
bucket_name: Name of the storage bucket
file_path: Path to the file in the bucket
Returns:
Public URL for the file
"""
return f"{SUPABASE_URL}/storage/v1/object/public/{bucket_name}/{file_path}"
def list_bucket_files(bucket_name: str = "images") -> Dict[str, Any]:
"""
List all files in a storage bucket.
Args:
bucket_name: Name of the storage bucket
Returns:
Dictionary with list of files
"""
try:
list_url = f"{STORAGE_BASE_URL}/object/list/{bucket_name}"
response = requests.get(list_url, headers=HEADERS)
if response.status_code == 200:
result = response.json()
logger.info(f"✅ Listed {len(result)} files in bucket '{bucket_name}'")
return {
"success": True,
"files": result
}
else:
logger.error(f"❌ Failed to list files: {response.status_code}")
return {
"success": False,
"error": f"Failed to list files: {response.status_code}"
}
except Exception as e:
logger.error(f"❌ Error listing files: {e}")
return {
"success": False,
"error": str(e)
}
def test_upload_image_png():
"""Test function to upload Image.png to the images bucket."""
try:
logger.info("🚀 Testing file upload to Supabase storage...")
# First, test if we can list files (this tests basic connectivity)
logger.info("📋 Testing bucket connectivity...")
list_result = list_bucket_files("images")
if list_result["success"]:
logger.info(f"✅ Bucket connectivity successful! Found {len(list_result['files'])} files")
else:
logger.warning(f"⚠️ Bucket connectivity issue: {list_result['error']}")
# Test upload
logger.info("📤 Testing file upload...")
result = upload_file_to_bucket(
file_path="Image.png",
bucket_name="images",
storage_path="test/Image.png",
file_options={"cache-control": "3600", "upsert": "false"}
)
if result["success"]:
logger.info("✅ Upload test successful!")
logger.info(f"📁 File uploaded to: {result['storage_path']}")
logger.info(f"🔗 Public URL: {result['public_url']}")
return True
else:
logger.error(f"❌ Upload test failed: {result['error']}")
# Provide helpful error information
if "row-level security policy" in result.get('response', ''):
logger.error("🔧 This is a storage permissions issue.")
logger.error("📋 To fix this, you need to configure your Supabase storage bucket:")
logger.error("""
1. Go to your Supabase dashboard
2. Navigate to Storage > Policies
3. For the 'images' bucket, add a policy like:
CREATE POLICY "Allow public uploads" ON storage.objects
FOR INSERT WITH CHECK (bucket_id = 'images');
CREATE POLICY "Allow public reads" ON storage.objects
FOR SELECT USING (bucket_id = 'images');
Or make the bucket public in Storage > Settings
""")
return False
except Exception as e:
logger.error(f"❌ Upload test error: {e}")
return False |