Spaces:
Sleeping
Sleeping
add permission
Browse files- Dockerfile +10 -6
- app.py +57 -18
Dockerfile
CHANGED
|
@@ -8,6 +8,11 @@ RUN apt-get update && \
|
|
| 8 |
build-essential \
|
| 9 |
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
# Copy requirements first to leverage Docker cache
|
| 12 |
COPY requirements.txt .
|
| 13 |
RUN pip install --no-cache-dir -r requirements.txt
|
|
@@ -16,12 +21,8 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|
| 16 |
COPY . .
|
| 17 |
|
| 18 |
# Create upload directory with proper permissions
|
| 19 |
-
RUN mkdir -p static/uploads && \
|
| 20 |
-
chmod -R 777 static
|
| 21 |
-
|
| 22 |
-
# Create metadata directory with proper permissions
|
| 23 |
-
RUN mkdir -p static/metadata && \
|
| 24 |
-
chmod -R 777 static/metadata
|
| 25 |
|
| 26 |
# Set environment variables for Hugging Face
|
| 27 |
ENV PYTHONUNBUFFERED=1
|
|
@@ -37,5 +38,8 @@ ENV ENV=production
|
|
| 37 |
# Expose port for Hugging Face Spaces (uses port 7860)
|
| 38 |
EXPOSE 7860
|
| 39 |
|
|
|
|
|
|
|
|
|
|
| 40 |
# Run the application
|
| 41 |
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
|
| 8 |
build-essential \
|
| 9 |
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
|
| 11 |
+
# Create a non-root user to run the application
|
| 12 |
+
RUN useradd -m appuser && \
|
| 13 |
+
mkdir -p /home/appuser/app /home/appuser/.cache && \
|
| 14 |
+
chown -R appuser:appuser /home/appuser
|
| 15 |
+
|
| 16 |
# Copy requirements first to leverage Docker cache
|
| 17 |
COPY requirements.txt .
|
| 18 |
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
|
| 21 |
COPY . .
|
| 22 |
|
| 23 |
# Create upload directory with proper permissions
|
| 24 |
+
RUN mkdir -p static/uploads static/metadata && \
|
| 25 |
+
chmod -R 777 static
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
# Set environment variables for Hugging Face
|
| 28 |
ENV PYTHONUNBUFFERED=1
|
|
|
|
| 38 |
# Expose port for Hugging Face Spaces (uses port 7860)
|
| 39 |
EXPOSE 7860
|
| 40 |
|
| 41 |
+
# Switch to the non-root user
|
| 42 |
+
# USER appuser
|
| 43 |
+
|
| 44 |
# Run the application
|
| 45 |
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
app.py
CHANGED
|
@@ -19,6 +19,7 @@ import io
|
|
| 19 |
from huggingface_hub import HfApi, HfFolder, create_repo
|
| 20 |
from huggingface_hub.utils import RepositoryNotFoundError
|
| 21 |
from huggingface_hub.hf_api import RepoFile
|
|
|
|
| 22 |
|
| 23 |
# Create FastAPI app
|
| 24 |
app = FastAPI(title="Image Uploader")
|
|
@@ -63,6 +64,13 @@ DATASET_REPO = os.environ.get("HF_DATASET_REPO", "image-uploader-data")
|
|
| 63 |
IMAGES_PATH = "images"
|
| 64 |
METADATA_PATH = "metadata"
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
# Initialize HfApi
|
| 67 |
hf_api = HfApi(token=HF_TOKEN)
|
| 68 |
|
|
@@ -71,22 +79,38 @@ def ensure_repo_exists():
|
|
| 71 |
try:
|
| 72 |
# Check if repo exists
|
| 73 |
hf_api.repo_info(repo_id=f"{HF_USERNAME}/{DATASET_REPO}", repo_type="dataset")
|
|
|
|
| 74 |
except RepositoryNotFoundError:
|
| 75 |
# Create repo if it doesn't exist
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
# Initialize repository if in production
|
| 88 |
if os.environ.get("ENV", "development") == "production":
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
def get_file_extension(filename: str) -> str:
|
| 92 |
"""Get the file extension from a filename."""
|
|
@@ -114,14 +138,17 @@ def verify_auth(request: Request):
|
|
| 114 |
def get_image_metadata():
|
| 115 |
"""Get all image metadata including hashtags."""
|
| 116 |
# In production, get metadata from Hugging Face
|
| 117 |
-
if os.environ.get("ENV", "development") == "production" and HF_USERNAME:
|
| 118 |
try:
|
|
|
|
| 119 |
metadata_file = hf_api.hf_hub_download(
|
| 120 |
repo_id=f"{HF_USERNAME}/{DATASET_REPO}",
|
| 121 |
filename=f"{METADATA_PATH}/image_metadata.json",
|
| 122 |
repo_type="dataset",
|
| 123 |
-
token=HF_TOKEN
|
|
|
|
| 124 |
)
|
|
|
|
| 125 |
with open(metadata_file, "r") as f:
|
| 126 |
return json.load(f)
|
| 127 |
except Exception as e:
|
|
@@ -139,6 +166,7 @@ def save_image_metadata(metadata):
|
|
| 139 |
# In production, save to Hugging Face
|
| 140 |
if os.environ.get("ENV", "development") == "production" and HF_USERNAME and HF_TOKEN:
|
| 141 |
try:
|
|
|
|
| 142 |
metadata_str = json.dumps(metadata)
|
| 143 |
hf_api.upload_file(
|
| 144 |
path_or_fileobj=io.BytesIO(metadata_str.encode()),
|
|
@@ -147,12 +175,16 @@ def save_image_metadata(metadata):
|
|
| 147 |
repo_type="dataset",
|
| 148 |
token=HF_TOKEN
|
| 149 |
)
|
|
|
|
| 150 |
except Exception as e:
|
| 151 |
print(f"Error saving metadata to Hugging Face: {e}")
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
def add_hashtags_to_image(filename, hashtags, original_filename=None):
|
| 158 |
"""Add hashtags to an image."""
|
|
@@ -182,6 +214,7 @@ def upload_to_hf(file_content, filename):
|
|
| 182 |
"""Upload a file to Hugging Face Dataset Repository."""
|
| 183 |
if os.environ.get("ENV", "development") == "production" and HF_USERNAME and HF_TOKEN:
|
| 184 |
try:
|
|
|
|
| 185 |
hf_api.upload_file(
|
| 186 |
path_or_fileobj=io.BytesIO(file_content),
|
| 187 |
path_in_repo=f"{IMAGES_PATH}/{filename}",
|
|
@@ -189,6 +222,7 @@ def upload_to_hf(file_content, filename):
|
|
| 189 |
repo_type="dataset",
|
| 190 |
token=HF_TOKEN
|
| 191 |
)
|
|
|
|
| 192 |
return True
|
| 193 |
except Exception as e:
|
| 194 |
print(f"Error uploading to Hugging Face: {e}")
|
|
@@ -199,12 +233,14 @@ def delete_from_hf(filename):
|
|
| 199 |
"""Delete a file from Hugging Face Dataset Repository."""
|
| 200 |
if os.environ.get("ENV", "development") == "production" and HF_USERNAME and HF_TOKEN:
|
| 201 |
try:
|
|
|
|
| 202 |
hf_api.delete_file(
|
| 203 |
path_in_repo=f"{IMAGES_PATH}/{filename}",
|
| 204 |
repo_id=f"{HF_USERNAME}/{DATASET_REPO}",
|
| 205 |
repo_type="dataset",
|
| 206 |
token=HF_TOKEN
|
| 207 |
)
|
|
|
|
| 208 |
return True
|
| 209 |
except Exception as e:
|
| 210 |
print(f"Error deleting from Hugging Face: {e}")
|
|
@@ -221,6 +257,7 @@ def list_hf_images():
|
|
| 221 |
"""List all images in the Hugging Face repo."""
|
| 222 |
if os.environ.get("ENV", "development") == "production" and HF_USERNAME and HF_TOKEN:
|
| 223 |
try:
|
|
|
|
| 224 |
files = hf_api.list_repo_files(
|
| 225 |
repo_id=f"{HF_USERNAME}/{DATASET_REPO}",
|
| 226 |
repo_type="dataset",
|
|
@@ -228,7 +265,9 @@ def list_hf_images():
|
|
| 228 |
)
|
| 229 |
# Filter only image files in the images directory
|
| 230 |
image_files = [f for f in files if f.startswith(f"{IMAGES_PATH}/")]
|
| 231 |
-
|
|
|
|
|
|
|
| 232 |
except Exception as e:
|
| 233 |
print(f"Error listing files from Hugging Face: {e}")
|
| 234 |
return []
|
|
|
|
| 19 |
from huggingface_hub import HfApi, HfFolder, create_repo
|
| 20 |
from huggingface_hub.utils import RepositoryNotFoundError
|
| 21 |
from huggingface_hub.hf_api import RepoFile
|
| 22 |
+
import tempfile
|
| 23 |
|
| 24 |
# Create FastAPI app
|
| 25 |
app = FastAPI(title="Image Uploader")
|
|
|
|
| 64 |
IMAGES_PATH = "images"
|
| 65 |
METADATA_PATH = "metadata"
|
| 66 |
|
| 67 |
+
# Set HF cache directory to a writable location
|
| 68 |
+
# This is necessary for Hugging Face Spaces which has permission issues with the default cache location
|
| 69 |
+
os.environ["HF_HOME"] = os.path.join(tempfile.gettempdir(), "huggingface")
|
| 70 |
+
os.environ["HUGGINGFACE_HUB_CACHE"] = os.path.join(tempfile.gettempdir(), "huggingface", "hub")
|
| 71 |
+
os.makedirs(os.environ["HF_HOME"], exist_ok=True)
|
| 72 |
+
os.makedirs(os.environ["HUGGINGFACE_HUB_CACHE"], exist_ok=True)
|
| 73 |
+
|
| 74 |
# Initialize HfApi
|
| 75 |
hf_api = HfApi(token=HF_TOKEN)
|
| 76 |
|
|
|
|
| 79 |
try:
|
| 80 |
# Check if repo exists
|
| 81 |
hf_api.repo_info(repo_id=f"{HF_USERNAME}/{DATASET_REPO}", repo_type="dataset")
|
| 82 |
+
print(f"Repository {HF_USERNAME}/{DATASET_REPO} exists")
|
| 83 |
except RepositoryNotFoundError:
|
| 84 |
# Create repo if it doesn't exist
|
| 85 |
+
try:
|
| 86 |
+
print(f"Creating repository {HF_USERNAME}/{DATASET_REPO}")
|
| 87 |
+
create_repo(f"{HF_USERNAME}/{DATASET_REPO}", repo_type="dataset", token=HF_TOKEN)
|
| 88 |
+
# Initialize metadata
|
| 89 |
+
metadata = json.dumps({})
|
| 90 |
+
hf_api.upload_file(
|
| 91 |
+
path_or_fileobj=io.BytesIO(metadata.encode()),
|
| 92 |
+
path_in_repo=f"{METADATA_PATH}/image_metadata.json",
|
| 93 |
+
repo_id=f"{HF_USERNAME}/{DATASET_REPO}",
|
| 94 |
+
repo_type="dataset",
|
| 95 |
+
token=HF_TOKEN
|
| 96 |
+
)
|
| 97 |
+
print(f"Repository created and initialized")
|
| 98 |
+
except Exception as e:
|
| 99 |
+
print(f"Error creating repository: {e}")
|
| 100 |
+
except Exception as e:
|
| 101 |
+
print(f"Error checking repository: {e}")
|
| 102 |
|
| 103 |
# Initialize repository if in production
|
| 104 |
if os.environ.get("ENV", "development") == "production":
|
| 105 |
+
print("Running in production mode, checking Hugging Face repository...")
|
| 106 |
+
if HF_USERNAME and HF_TOKEN:
|
| 107 |
+
print(f"Using Hugging Face credentials for user: {HF_USERNAME}")
|
| 108 |
+
try:
|
| 109 |
+
ensure_repo_exists()
|
| 110 |
+
except Exception as e:
|
| 111 |
+
print(f"Error ensuring repository exists: {e}")
|
| 112 |
+
else:
|
| 113 |
+
print("Warning: HF_USERNAME or HF_TOKEN not set. Running without Hugging Face integration.")
|
| 114 |
|
| 115 |
def get_file_extension(filename: str) -> str:
|
| 116 |
"""Get the file extension from a filename."""
|
|
|
|
| 138 |
def get_image_metadata():
|
| 139 |
"""Get all image metadata including hashtags."""
|
| 140 |
# In production, get metadata from Hugging Face
|
| 141 |
+
if os.environ.get("ENV", "development") == "production" and HF_USERNAME and HF_TOKEN:
|
| 142 |
try:
|
| 143 |
+
print(f"Fetching metadata from Hugging Face repository {HF_USERNAME}/{DATASET_REPO}")
|
| 144 |
metadata_file = hf_api.hf_hub_download(
|
| 145 |
repo_id=f"{HF_USERNAME}/{DATASET_REPO}",
|
| 146 |
filename=f"{METADATA_PATH}/image_metadata.json",
|
| 147 |
repo_type="dataset",
|
| 148 |
+
token=HF_TOKEN,
|
| 149 |
+
local_dir=os.path.join(tempfile.gettempdir(), "hf_downloads")
|
| 150 |
)
|
| 151 |
+
print(f"Metadata downloaded to {metadata_file}")
|
| 152 |
with open(metadata_file, "r") as f:
|
| 153 |
return json.load(f)
|
| 154 |
except Exception as e:
|
|
|
|
| 166 |
# In production, save to Hugging Face
|
| 167 |
if os.environ.get("ENV", "development") == "production" and HF_USERNAME and HF_TOKEN:
|
| 168 |
try:
|
| 169 |
+
print(f"Saving metadata to Hugging Face repository {HF_USERNAME}/{DATASET_REPO}")
|
| 170 |
metadata_str = json.dumps(metadata)
|
| 171 |
hf_api.upload_file(
|
| 172 |
path_or_fileobj=io.BytesIO(metadata_str.encode()),
|
|
|
|
| 175 |
repo_type="dataset",
|
| 176 |
token=HF_TOKEN
|
| 177 |
)
|
| 178 |
+
print(f"Metadata saved successfully")
|
| 179 |
except Exception as e:
|
| 180 |
print(f"Error saving metadata to Hugging Face: {e}")
|
| 181 |
+
# Still save locally as fallback
|
| 182 |
+
with open(METADATA_FILE, "w") as f:
|
| 183 |
+
json.dump(metadata, f)
|
| 184 |
+
else:
|
| 185 |
+
# Local development or fallback
|
| 186 |
+
with open(METADATA_FILE, "w") as f:
|
| 187 |
+
json.dump(metadata, f)
|
| 188 |
|
| 189 |
def add_hashtags_to_image(filename, hashtags, original_filename=None):
|
| 190 |
"""Add hashtags to an image."""
|
|
|
|
| 214 |
"""Upload a file to Hugging Face Dataset Repository."""
|
| 215 |
if os.environ.get("ENV", "development") == "production" and HF_USERNAME and HF_TOKEN:
|
| 216 |
try:
|
| 217 |
+
print(f"Uploading file {filename} to Hugging Face repository {HF_USERNAME}/{DATASET_REPO}")
|
| 218 |
hf_api.upload_file(
|
| 219 |
path_or_fileobj=io.BytesIO(file_content),
|
| 220 |
path_in_repo=f"{IMAGES_PATH}/{filename}",
|
|
|
|
| 222 |
repo_type="dataset",
|
| 223 |
token=HF_TOKEN
|
| 224 |
)
|
| 225 |
+
print(f"File {filename} uploaded successfully")
|
| 226 |
return True
|
| 227 |
except Exception as e:
|
| 228 |
print(f"Error uploading to Hugging Face: {e}")
|
|
|
|
| 233 |
"""Delete a file from Hugging Face Dataset Repository."""
|
| 234 |
if os.environ.get("ENV", "development") == "production" and HF_USERNAME and HF_TOKEN:
|
| 235 |
try:
|
| 236 |
+
print(f"Deleting file {filename} from Hugging Face repository {HF_USERNAME}/{DATASET_REPO}")
|
| 237 |
hf_api.delete_file(
|
| 238 |
path_in_repo=f"{IMAGES_PATH}/{filename}",
|
| 239 |
repo_id=f"{HF_USERNAME}/{DATASET_REPO}",
|
| 240 |
repo_type="dataset",
|
| 241 |
token=HF_TOKEN
|
| 242 |
)
|
| 243 |
+
print(f"File {filename} deleted successfully")
|
| 244 |
return True
|
| 245 |
except Exception as e:
|
| 246 |
print(f"Error deleting from Hugging Face: {e}")
|
|
|
|
| 257 |
"""List all images in the Hugging Face repo."""
|
| 258 |
if os.environ.get("ENV", "development") == "production" and HF_USERNAME and HF_TOKEN:
|
| 259 |
try:
|
| 260 |
+
print(f"Listing files from Hugging Face repository {HF_USERNAME}/{DATASET_REPO}")
|
| 261 |
files = hf_api.list_repo_files(
|
| 262 |
repo_id=f"{HF_USERNAME}/{DATASET_REPO}",
|
| 263 |
repo_type="dataset",
|
|
|
|
| 265 |
)
|
| 266 |
# Filter only image files in the images directory
|
| 267 |
image_files = [f for f in files if f.startswith(f"{IMAGES_PATH}/")]
|
| 268 |
+
image_basenames = [os.path.basename(f) for f in image_files]
|
| 269 |
+
print(f"Found {len(image_basenames)} images")
|
| 270 |
+
return image_basenames
|
| 271 |
except Exception as e:
|
| 272 |
print(f"Error listing files from Hugging Face: {e}")
|
| 273 |
return []
|