Translsis commited on
Commit
96a575a
·
verified ·
1 Parent(s): 88c18c0

Upload: app(2).py

Browse files
Files changed (1) hide show
  1. app(2).py +501 -0
app(2).py ADDED
@@ -0,0 +1,501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import logging
4
+ import asyncio
5
+ import tempfile
6
+ import git
7
+ import shutil
8
+ from pathlib import Path
9
+ from typing import Tuple, Optional
10
+ from huggingface_hub import login, HfApi, create_repo, upload_file
11
+ from urllib.parse import urlparse
12
+ from functools import partial
13
+
14
+ # Setup logging
15
+ logging.basicConfig(
16
+ level=logging.INFO,
17
+ format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
18
+ )
19
+ logger = logging.getLogger(__name__)
20
+
21
+ class HuggingFaceManager:
22
+ def __init__(self):
23
+ self.api = HfApi()
24
+ self.token: Optional[str] = None
25
+ self.temp_dir = Path(tempfile.mkdtemp())
26
+
27
+ def cleanup(self):
28
+ """Clean up temporary directory"""
29
+ if self.temp_dir.exists():
30
+ shutil.rmtree(self.temp_dir, ignore_errors=True)
31
+
32
+ def validate_repo_name(self, repo_name: str) -> bool:
33
+ """Validate repository name format"""
34
+ if not repo_name or '/' not in repo_name:
35
+ raise ValueError("Repository name must be in format 'username/repo-name'")
36
+ username, repo = repo_name.split('/', 1)
37
+ return all(name.strip() for name in [username, repo])
38
+
39
+ def validate_url(self, url: str) -> bool:
40
+ """Validate URL format"""
41
+ try:
42
+ result = urlparse(url)
43
+ return all([result.scheme, result.netloc])
44
+ except Exception:
45
+ return False
46
+
47
+ async def login_and_validate(self, token: str) -> str:
48
+ """Login to Hugging Face and validate token"""
49
+ if not token.strip():
50
+ return "Error: Token cannot be empty"
51
+
52
+ try:
53
+ loop = asyncio.get_event_loop()
54
+ await loop.run_in_executor(None, login, token)
55
+ await loop.run_in_executor(None, self.api.whoami)
56
+
57
+ self.token = token
58
+ return "Login successful!"
59
+ except Exception as e:
60
+ logger.error(f"Login failed: {e}")
61
+ return f"Login failed: {str(e)}"
62
+
63
+ async def download_from_url(
64
+ self,
65
+ url: str,
66
+ download_type: str,
67
+ progress: Optional[gr.Progress] = None
68
+ ) -> Tuple[str, Optional[str]]:
69
+ """Download content from URL using wget or git"""
70
+ if not self.validate_url(url):
71
+ return "Invalid URL format!", None
72
+
73
+ output_path = self.temp_dir / "downloaded_content"
74
+ if output_path.exists():
75
+ shutil.rmtree(output_path)
76
+ output_path.mkdir(parents=True)
77
+
78
+ try:
79
+ if download_type == "wget":
80
+ if progress:
81
+ progress(0, desc="Downloading with wget...")
82
+
83
+ process = await asyncio.create_subprocess_exec(
84
+ 'wget', '-P', str(output_path), url,
85
+ stdout=asyncio.subprocess.PIPE,
86
+ stderr=asyncio.subprocess.PIPE
87
+ )
88
+
89
+ try:
90
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=300)
91
+ if process.returncode != 0:
92
+ raise Exception(f"wget failed: {stderr.decode()}")
93
+ except asyncio.TimeoutError:
94
+ raise Exception("Download timed out after 5 minutes")
95
+
96
+ elif download_type == "git":
97
+ if progress:
98
+ progress(0, desc="Cloning git repository...")
99
+
100
+ await asyncio.get_event_loop().run_in_executor(
101
+ None,
102
+ partial(git.Repo.clone_from, url, str(output_path), depth=1)
103
+ )
104
+
105
+ if progress:
106
+ progress(1, desc="Download complete")
107
+
108
+ return "Download successful!", str(output_path)
109
+
110
+ except Exception as e:
111
+ logger.error(f"Download failed: {e}")
112
+ if output_path.exists():
113
+ shutil.rmtree(output_path)
114
+ return f"Download failed: {str(e)}", None
115
+
116
+ async def create_repository(
117
+ self,
118
+ repo_name: str,
119
+ repo_type: str,
120
+ is_private: bool
121
+ ) -> str:
122
+ """Create a new repository on Hugging Face"""
123
+ if not self.token:
124
+ return "Please login first!"
125
+
126
+ try:
127
+ self.validate_repo_name(repo_name)
128
+
129
+ await asyncio.get_event_loop().run_in_executor(
130
+ None,
131
+ partial(
132
+ create_repo,
133
+ repo_name,
134
+ private=is_private,
135
+ repo_type=repo_type,
136
+ exist_ok=True
137
+ )
138
+ )
139
+
140
+ return f"Repository '{repo_name}' created successfully!"
141
+ except Exception as e:
142
+ logger.error(f"Failed to create repository: {e}")
143
+ return f"Failed to create repository: {str(e)}"
144
+
145
+ async def upload_file_worker(
146
+ self,
147
+ file_path: Path,
148
+ relative_path: Path,
149
+ repo_name: str,
150
+ repo_type: str,
151
+ progress_callback=None
152
+ ) -> Optional[str]:
153
+ """Upload a single file to repository"""
154
+ try:
155
+ await asyncio.get_event_loop().run_in_executor(
156
+ None,
157
+ partial(
158
+ upload_file,
159
+ path_or_fileobj=str(file_path),
160
+ path_in_repo=str(relative_path),
161
+ repo_id=repo_name,
162
+ repo_type=repo_type,
163
+ commit_message=f"Upload: {relative_path}"
164
+ )
165
+ )
166
+ if progress_callback:
167
+ progress_callback()
168
+ return str(relative_path)
169
+ except Exception as e:
170
+ logger.error(f"Error uploading {relative_path}: {e}")
171
+ return None
172
+
173
+ async def upload_folder(
174
+ self,
175
+ folder_path: str,
176
+ repo_name: str,
177
+ repo_type: str,
178
+ progress: Optional[gr.Progress] = None
179
+ ) -> str:
180
+ """Upload entire folder to repository"""
181
+ if not self.token:
182
+ return "Please login first!"
183
+
184
+ folder_path = Path(folder_path)
185
+ if not folder_path.exists():
186
+ return f"Folder {folder_path} does not exist!"
187
+
188
+ try:
189
+ self.validate_repo_name(repo_name)
190
+
191
+ # Collect files for upload
192
+ files_to_upload = []
193
+ for root, dirs, files in os.walk(folder_path):
194
+ if '.git' in dirs:
195
+ dirs.remove('.git')
196
+
197
+ for file in files:
198
+ file_path = Path(root) / file
199
+ relative_path = file_path.relative_to(folder_path)
200
+ files_to_upload.append((file_path, relative_path))
201
+
202
+ if not files_to_upload:
203
+ return "No files found to upload"
204
+
205
+ if progress:
206
+ progress(0, desc=f"Uploading {len(files_to_upload)} files...")
207
+
208
+ # Track progress
209
+ uploaded_count = 0
210
+ def update_progress():
211
+ nonlocal uploaded_count
212
+ uploaded_count += 1
213
+ if progress:
214
+ progress(uploaded_count / len(files_to_upload))
215
+
216
+ # Upload files in parallel
217
+ tasks = [
218
+ self.upload_file_worker(
219
+ file_path,
220
+ relative_path,
221
+ repo_name,
222
+ repo_type,
223
+ update_progress
224
+ )
225
+ for file_path, relative_path in files_to_upload
226
+ ]
227
+
228
+ results = await asyncio.gather(*tasks, return_exceptions=True)
229
+
230
+ successful = sum(1 for r in results if r is not None)
231
+ failed = len(files_to_upload) - successful
232
+
233
+ return f"Uploaded {successful} files successfully. Failed: {failed}"
234
+
235
+ except Exception as e:
236
+ logger.error(f"Upload failed: {e}")
237
+ return f"Upload failed: {str(e)}"
238
+
239
+ async def delete_files(
240
+ self,
241
+ folder_path: str,
242
+ repo_name: str,
243
+ repo_type: str,
244
+ dry_run: bool = True,
245
+ progress: Optional[gr.Progress] = None
246
+ ) -> str:
247
+ """Delete files from repository based on local folder structure"""
248
+ if not self.token:
249
+ return "Please login first!"
250
+
251
+ try:
252
+ self.validate_repo_name(repo_name)
253
+
254
+ folder_path = Path(folder_path)
255
+ if not folder_path.exists():
256
+ return f"Folder {folder_path} does not exist!"
257
+
258
+ # Get repository files
259
+ existing_files = await asyncio.get_event_loop().run_in_executor(
260
+ None,
261
+ partial(self.api.list_repo_files, repo_id=repo_name, repo_type=repo_type)
262
+ )
263
+
264
+ # Find files to delete
265
+ files_to_delete = []
266
+ for root, dirs, files in os.walk(folder_path):
267
+ if '.git' in dirs:
268
+ dirs.remove('.git')
269
+
270
+ for file in files:
271
+ file_path = Path(root) / file
272
+ relative_path = str(file_path.relative_to(folder_path))
273
+ if relative_path in existing_files:
274
+ files_to_delete.append(relative_path)
275
+
276
+ if not files_to_delete:
277
+ return "No matching files found to delete"
278
+
279
+ if dry_run:
280
+ return f"Dry run: Would delete {len(files_to_delete)} files"
281
+
282
+ # Perform deletion
283
+ if progress:
284
+ progress(0, desc=f"Deleting {len(files_to_delete)} files...")
285
+
286
+ deleted_count = 0
287
+ for file_path in files_to_delete:
288
+ try:
289
+ await asyncio.get_event_loop().run_in_executor(
290
+ None,
291
+ partial(
292
+ self.api.delete_file,
293
+ repo_id=repo_name,
294
+ path_in_repo=file_path,
295
+ repo_type=repo_type
296
+ )
297
+ )
298
+ deleted_count += 1
299
+ if progress:
300
+ progress(deleted_count / len(files_to_delete))
301
+ except Exception as e:
302
+ logger.error(f"Error deleting {file_path}: {e}")
303
+
304
+ return f"Successfully deleted {deleted_count} out of {len(files_to_delete)} files"
305
+
306
+ except Exception as e:
307
+ logger.error(f"Delete operation failed: {e}")
308
+ return f"Delete operation failed: {str(e)}"
309
+
310
+ def create_interface() -> gr.Blocks:
311
+ """Create Gradio interface"""
312
+ manager = HuggingFaceManager()
313
+
314
+ with gr.Blocks(title="Hugging Face Repository Manager") as app:
315
+ gr.Markdown("# Hugging Face Repository Manager")
316
+
317
+ # Login Tab
318
+ with gr.Tab("Login"):
319
+ token_input = gr.Textbox(
320
+ label="Hugging Face Token",
321
+ type="password",
322
+ placeholder="Enter your Hugging Face token"
323
+ )
324
+ login_btn = gr.Button("Login", variant="primary")
325
+ login_output = gr.Textbox(label="Login Status", interactive=False)
326
+
327
+ login_btn.click(
328
+ fn=lambda x: asyncio.run(manager.login_and_validate(x)),
329
+ inputs=[token_input],
330
+ outputs=[login_output]
331
+ )
332
+
333
+ # Download Tab
334
+ with gr.Tab("Download"):
335
+ download_url = gr.Textbox(
336
+ label="URL",
337
+ placeholder="Enter URL to download"
338
+ )
339
+ download_type = gr.Radio(
340
+ choices=["wget", "git"],
341
+ label="Download Type",
342
+ value="wget"
343
+ )
344
+ download_btn = gr.Button("Download", variant="primary")
345
+ download_output = gr.Textbox(label="Status", interactive=False)
346
+ download_path = gr.Textbox(
347
+ label="Download Path",
348
+ interactive=False,
349
+ visible=False
350
+ )
351
+
352
+ download_btn.click(
353
+ fn=lambda x, y: asyncio.run(manager.download_from_url(x, y)),
354
+ inputs=[download_url, download_type],
355
+ outputs=[download_output, download_path]
356
+ )
357
+
358
+ # Create Repository Tab
359
+ with gr.Tab("Create Repository"):
360
+ repo_name = gr.Textbox(
361
+ label="Repository Name",
362
+ placeholder="username/repo-name"
363
+ )
364
+ repo_type = gr.Radio(
365
+ choices=["model", "dataset", "space"],
366
+ label="Repository Type",
367
+ value="model"
368
+ )
369
+ is_private = gr.Checkbox(label="Private Repository", value=True)
370
+ create_btn = gr.Button("Create Repository", variant="primary")
371
+ create_output = gr.Textbox(label="Status", interactive=False)
372
+
373
+ create_btn.click(
374
+ fn=lambda x, y, z: asyncio.run(manager.create_repository(x, y, z)),
375
+ inputs=[repo_name, repo_type, is_private],
376
+ outputs=[create_output]
377
+ )
378
+
379
+ # Upload Tab
380
+ with gr.Tab("Upload"):
381
+ with gr.Row():
382
+ upload_folder_path = gr.Textbox(
383
+ label="Local Folder Path",
384
+ placeholder="Path to local folder"
385
+ )
386
+ use_downloaded = gr.Checkbox(
387
+ label="Use Downloaded Content",
388
+ value=False
389
+ )
390
+
391
+ upload_repo_name = gr.Textbox(
392
+ label="Repository Name",
393
+ placeholder="username/repo-name"
394
+ )
395
+ upload_repo_type = gr.Radio(
396
+ choices=["model", "dataset", "space"],
397
+ label="Repository Type",
398
+ value="model"
399
+ )
400
+ upload_btn = gr.Button("Upload Files", variant="primary")
401
+ upload_output = gr.Textbox(label="Status", interactive=False)
402
+
403
+ def prepare_upload_path(folder_path, use_downloaded, downloaded_path):
404
+ return downloaded_path if use_downloaded and downloaded_path else folder_path
405
+
406
+ upload_btn.click(
407
+ fn=lambda *args: asyncio.run(
408
+ manager.upload_folder(
409
+ prepare_upload_path(args[0], args[1], args[2]),
410
+ args[3],
411
+ args[4]
412
+ )
413
+ ),
414
+ inputs=[
415
+ upload_folder_path,
416
+ use_downloaded,
417
+ download_path,
418
+ upload_repo_name,
419
+ upload_repo_type
420
+ ],
421
+ outputs=[upload_output]
422
+ )
423
+
424
+ # Delete Tab
425
+ with gr.Tab("Delete"):
426
+ delete_folder_path = gr.Textbox(
427
+ label="Local Folder Path",
428
+ placeholder="Path to local folder for reference"
429
+ )
430
+ delete_repo_name = gr.Textbox(
431
+ label="Repository Name",
432
+ placeholder="username/repo-name"
433
+ )
434
+ delete_repo_type = gr.Radio(
435
+ choices=["model", "dataset", "space"],
436
+ label="Repository Type",
437
+ value="model"
438
+ )
439
+ dry_run = gr.Checkbox(
440
+ label="Dry Run",
441
+ value=True,
442
+ info="Preview changes without making them"
443
+ )
444
+ delete_btn = gr.Button("Delete Files", variant="secondary")
445
+ delete_output = gr.Textbox(label="Status", interactive=False)
446
+
447
+ delete_btn.click(
448
+ fn=lambda *args: asyncio.run(manager.delete_files(*args)),
449
+ inputs=[
450
+ delete_folder_path,
451
+ delete_repo_name,
452
+ delete_repo_type,
453
+ dry_run
454
+ ],
455
+ outputs=[delete_output]
456
+ )
457
+
458
+ # Error handling
459
+ gr.Error()
460
+
461
+ # Footer information
462
+ gr.Markdown("""
463
+ ### Information
464
+ - Get your token from [Hugging Face Settings](https://huggingface.co/settings/tokens)
465
+ - Repository names must be in format: username/repository-name
466
+ - For files larger than 5GB, use Git LFS
467
+ - Repository types:
468
+ - Model: For ML models and weights
469
+ - Dataset: For datasets and data files
470
+ - Space: For demos and applications
471
+ """)
472
+
473
+ return app
474
+
475
+ def main():
476
+ """Application entry point"""
477
+ try:
478
+ logger.info("Starting Hugging Face Repository Manager")
479
+ app = create_interface()
480
+
481
+ # Configure and launch the application
482
+ app.queue()
483
+ app.launch(
484
+ server_name="0.0.0.0",
485
+ server_port=7860,
486
+ share=True,
487
+ max_threads=2,
488
+ # Security configurations
489
+ auth=None,
490
+ ssl_keyfile=None,
491
+ ssl_certfile=None,
492
+ ssl_verify=True
493
+ )
494
+ except Exception as e:
495
+ logger.error(f"Application failed to start: {e}")
496
+ raise
497
+ finally:
498
+ logger.info("Shutting down Hugging Face Repository Manager")
499
+
500
+ if __name__ == "__main__":
501
+ main()