Alamgirapi commited on
Commit
bfb22dc
·
verified ·
1 Parent(s): 888abd6

Upload via Flask App

Browse files
Files changed (1) hide show
  1. app.py +276 -0
app.py ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+ import zipfile
5
+ from datetime import datetime
6
+ from flask import Flask, render_template, request, jsonify, flash, redirect, url_for
7
+ from werkzeug.utils import secure_filename
8
+ from huggingface_hub import HfApi, upload_folder
9
+ import threading
10
+ import time
11
+ import logging
12
+ import traceback
13
+
14
+ # Configure logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ app = Flask(__name__)
19
+ app.secret_key = os.environ.get('SECRET_KEY', 'your-secret-key-change-this')
20
+
21
+ # Configuration
22
+ UPLOAD_FOLDER = 'uploads'
23
+ ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'py', 'js', 'html', 'css', 'json', 'md', 'zip', 'yml', 'yaml', 'toml', 'cfg', 'ini', 'conf', 'log', 'xml', 'svg', 'ico', 'woff', 'woff2', 'ttf', 'eot', 'mp4', 'mp3', 'wav', 'avi', 'mov', 'webm', 'webp', 'tiff', 'bmp'}
24
+ # Common files without extensions
25
+ ALLOWED_NO_EXTENSION_FILES = {'dockerfile', 'makefile', 'readme', 'license', 'changelog', 'authors', 'contributors', 'copying', 'install', 'news', 'todo', 'requirements', 'pipfile', 'procfile', 'cmakelists', 'gitignore', 'gitattributes', 'dockerignore', 'editorconfig', 'flake8', 'pylintrc', 'pre-commit-config', 'travis', 'appveyor', 'circle', 'azure-pipelines'}
26
+
27
+ MAX_CONTENT_LENGTH = 500 * 1024 * 1024 # 500MB max file size
28
+
29
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
30
+ app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
31
+
32
+ # Ensure upload directory exists
33
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
34
+
35
+ # Global variables for tracking upload progress
36
+ upload_progress = {}
37
+ upload_status = {}
38
+
39
+ def allowed_file(filename):
40
+ """Check if file is allowed based on extension or known files without extensions"""
41
+ if '.' in filename:
42
+ extension = filename.rsplit('.', 1)[1].lower()
43
+ return extension in ALLOWED_EXTENSIONS
44
+ else:
45
+ # Check if it's a known file without extension
46
+ return filename.lower() in ALLOWED_NO_EXTENSION_FILES
47
+
48
+ def extract_zip(zip_path, extract_to):
49
+ """Extract ZIP file to specified directory"""
50
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
51
+ zip_ref.extractall(extract_to)
52
+
53
+ def create_zip_from_files(files, zip_path):
54
+ """Create a ZIP file from uploaded files"""
55
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
56
+ for file in files:
57
+ if file.filename:
58
+ # Save file temporarily
59
+ temp_path = os.path.join(tempfile.gettempdir(), secure_filename(file.filename))
60
+ file.save(temp_path)
61
+
62
+ # Add to ZIP with the original filename
63
+ zipf.write(temp_path, file.filename)
64
+
65
+ # Clean up temp file
66
+ os.remove(temp_path)
67
+
68
+ def upload_to_huggingface(upload_id, folder_path, repo_id, hf_token, commit_message, repo_type="space"):
69
+ """Upload folder to Hugging Face in a separate thread"""
70
+ try:
71
+ logger.info(f"Starting upload {upload_id} to {repo_id}")
72
+ upload_status[upload_id] = "Initializing..."
73
+ upload_progress[upload_id] = 0
74
+
75
+ # Initialize API
76
+ logger.info(f"Initializing HfApi for {upload_id}")
77
+ api = HfApi(token=hf_token)
78
+
79
+ # Test token validity
80
+ try:
81
+ logger.info(f"Testing token validity for {upload_id}")
82
+ user_info = api.whoami()
83
+ logger.info(f"Token valid for user: {user_info.get('name', 'Unknown')}")
84
+ except Exception as e:
85
+ logger.error(f"Token validation failed for {upload_id}: {str(e)}")
86
+ upload_status[upload_id] = f"Invalid token: {str(e)}"
87
+ return
88
+
89
+ upload_status[upload_id] = "Uploading to Hugging Face..."
90
+ upload_progress[upload_id] = 25
91
+
92
+ logger.info(f"Starting upload for {upload_id}: {folder_path} -> {repo_id}")
93
+
94
+ # Always upload as folder to maintain directory structure
95
+ if os.path.isfile(folder_path):
96
+ logger.info(f"Converting single file to folder structure: {folder_path}")
97
+ # Create a temporary folder for single file
98
+ temp_folder = os.path.join(os.path.dirname(folder_path), f"temp_folder_{upload_id}")
99
+ os.makedirs(temp_folder, exist_ok=True)
100
+
101
+ # Copy the file to the temporary folder with original name
102
+ filename = os.path.basename(folder_path)
103
+ temp_file_path = os.path.join(temp_folder, filename)
104
+ shutil.copy2(folder_path, temp_file_path)
105
+
106
+ # Upload the temporary folder
107
+ upload_folder(
108
+ folder_path=temp_folder,
109
+ repo_id=repo_id,
110
+ repo_type=repo_type,
111
+ token=hf_token,
112
+ commit_message=commit_message,
113
+ ignore_patterns=[".git", "__pycache__", "*.pyc", ".env", "*.log"]
114
+ )
115
+
116
+ # Clean up temporary folder
117
+ shutil.rmtree(temp_folder)
118
+
119
+ else:
120
+ logger.info(f"Uploading folder: {folder_path}")
121
+ # Folder upload
122
+ upload_folder(
123
+ folder_path=folder_path,
124
+ repo_id=repo_id,
125
+ repo_type=repo_type,
126
+ token=hf_token,
127
+ commit_message=commit_message,
128
+ ignore_patterns=[".git", "__pycache__", "*.pyc", ".env", "*.log"]
129
+ )
130
+
131
+ upload_progress[upload_id] = 100
132
+ upload_status[upload_id] = "Upload completed successfully!"
133
+ logger.info(f"Upload {upload_id} completed successfully")
134
+
135
+ except Exception as e:
136
+ error_msg = f"Upload error: {str(e)}"
137
+ logger.error(f"Upload {upload_id} failed: {error_msg}")
138
+ logger.error(f"Full traceback: {traceback.format_exc()}")
139
+ upload_status[upload_id] = error_msg
140
+ upload_progress[upload_id] = 0
141
+
142
+ @app.route('/')
143
+ def index():
144
+ return render_template('index.html')
145
+
146
+ @app.route('/upload', methods=['POST'])
147
+ def upload_file():
148
+ try:
149
+ # Get form data
150
+ hf_token = request.form.get('hf_token')
151
+ repo_id = request.form.get('repo_id')
152
+ repo_type = request.form.get('repo_type', 'space')
153
+ commit_message = request.form.get('commit_message', 'Upload via Flask App')
154
+
155
+ logger.info(f"Upload request: repo_id={repo_id}, repo_type={repo_type}")
156
+
157
+ if not hf_token or not repo_id:
158
+ return jsonify({'error': 'Hugging Face token and repository ID are required'}), 400
159
+
160
+ # Check if files were uploaded
161
+ if 'files' not in request.files:
162
+ return jsonify({'error': 'No files selected'}), 400
163
+
164
+ files = request.files.getlist('files')
165
+ if not files or all(file.filename == '' for file in files):
166
+ return jsonify({'error': 'No files selected'}), 400
167
+
168
+ # Generate unique upload ID and timestamp for internal tracking
169
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
170
+ upload_id = f"upload_{timestamp}"
171
+
172
+ # Handle multiple files and folder structures
173
+ if len(files) == 1 and not any('/' in file.filename for file in files):
174
+ # Single file upload
175
+ file = files[0]
176
+ if file and allowed_file(file.filename):
177
+ # Use original filename without timestamp
178
+ filename = secure_filename(file.filename)
179
+
180
+ # Create unique folder for this upload to avoid conflicts
181
+ upload_folder_path = os.path.join(app.config['UPLOAD_FOLDER'], f"upload_{timestamp}")
182
+ os.makedirs(upload_folder_path, exist_ok=True)
183
+
184
+ # Save uploaded file with original name
185
+ file_path = os.path.join(upload_folder_path, filename)
186
+ file.save(file_path)
187
+ logger.info(f"File saved: {file_path}")
188
+
189
+ # Handle ZIP files
190
+ if filename.lower().endswith('.zip'):
191
+ extract_folder = os.path.join(upload_folder_path, "extracted")
192
+ os.makedirs(extract_folder, exist_ok=True)
193
+ extract_zip(file_path, extract_folder)
194
+ upload_path = extract_folder
195
+ logger.info(f"ZIP extracted to: {extract_folder}")
196
+ else:
197
+ upload_path = file_path
198
+ else:
199
+ return jsonify({'error': f'File type not allowed: {file.filename}'}), 400
200
+ else:
201
+ # Multiple files or folder structure - create folder structure
202
+ folder_path = os.path.join(app.config['UPLOAD_FOLDER'], f"folder_{timestamp}")
203
+ os.makedirs(folder_path, exist_ok=True)
204
+
205
+ # Save all files to the folder, preserving directory structure
206
+ for file in files:
207
+ if file and file.filename and allowed_file(file.filename):
208
+ # Use the original filename which may include directory structure
209
+ filename = file.filename
210
+ # Ensure the filename is safe but preserve directory structure
211
+ safe_filename = filename.replace('\\', '/').strip('/')
212
+ file_path = os.path.join(folder_path, safe_filename)
213
+
214
+ # Create subdirectories if needed (for folder uploads)
215
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
216
+ file.save(file_path)
217
+ logger.info(f"File saved: {file_path}")
218
+ elif file and file.filename:
219
+ return jsonify({'error': f'File type not allowed: {file.filename}'}), 400
220
+
221
+ upload_path = folder_path
222
+ logger.info(f"Folder created: {folder_path}")
223
+
224
+ # Start upload in background thread
225
+ thread = threading.Thread(
226
+ target=upload_to_huggingface,
227
+ args=(upload_id, upload_path, repo_id, hf_token, commit_message, repo_type)
228
+ )
229
+ thread.daemon = True
230
+ thread.start()
231
+
232
+ return jsonify({
233
+ 'success': True,
234
+ 'upload_id': upload_id,
235
+ 'message': 'Upload started successfully!'
236
+ })
237
+
238
+ except Exception as e:
239
+ logger.error(f"Upload endpoint error: {str(e)}")
240
+ logger.error(f"Full traceback: {traceback.format_exc()}")
241
+ return jsonify({'error': str(e)}), 500
242
+
243
+ @app.route('/progress/<upload_id>')
244
+ def get_progress(upload_id):
245
+ """Get upload progress"""
246
+ progress = upload_progress.get(upload_id, 0)
247
+ status = upload_status.get(upload_id, "Unknown")
248
+
249
+ return jsonify({
250
+ 'progress': progress,
251
+ 'status': status,
252
+ 'completed': progress == 100 and 'Error' not in status
253
+ })
254
+
255
+ @app.route('/cleanup')
256
+ def cleanup():
257
+ """Clean up old uploaded files"""
258
+ try:
259
+ # Remove files older than 1 hour
260
+ cutoff_time = time.time() - 3600 # 1 hour
261
+
262
+ for filename in os.listdir(app.config['UPLOAD_FOLDER']):
263
+ file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
264
+ if os.path.getctime(file_path) < cutoff_time:
265
+ if os.path.isfile(file_path):
266
+ os.remove(file_path)
267
+ elif os.path.isdir(file_path):
268
+ shutil.rmtree(file_path)
269
+
270
+ return jsonify({'message': 'Cleanup completed'})
271
+ except Exception as e:
272
+ return jsonify({'error': str(e)}), 500
273
+
274
+ if __name__ == '__main__':
275
+ port = int(os.environ.get('PORT', 7860)) # Hugging Face Spaces uses port 7860
276
+ app.run(debug=False, host='0.0.0.0', port=port, use_reloader=False)