Alamgirapi commited on
Commit
a7248ed
·
verified ·
1 Parent(s): 59b2bbb

Update app.py

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