Spaces:
Runtime error
Runtime error
add migratin script
Browse files- user_history.py +93 -3
user_history.py
CHANGED
@@ -1,3 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import json
|
2 |
import os
|
3 |
import shutil
|
@@ -20,6 +37,9 @@ def setup(folder_path: str | Path | None = None) -> None:
|
|
20 |
user_history.folder_path = _resolve_folder_path(folder_path)
|
21 |
user_history.initialized = True
|
22 |
|
|
|
|
|
|
|
23 |
|
24 |
def render() -> None:
|
25 |
user_history = _UserHistory()
|
@@ -46,9 +66,18 @@ def render() -> None:
|
|
46 |
with gr.Row():
|
47 |
gr.LoginButton(min_width=250)
|
48 |
gr.LogoutButton(min_width=250)
|
49 |
-
refresh_button = gr.Button(
|
50 |
-
|
51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
|
53 |
# "Export zip" row (hidden by default)
|
54 |
with gr.Row():
|
@@ -395,3 +424,64 @@ def _fetch_admins() -> List[str]:
|
|
395 |
if response.status_code == 200:
|
396 |
return sorted((member["user"] for member in response.json()), key=lambda x: x.lower())
|
397 |
return [namespace]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
User History is a plugin that you can add to your Spaces to cache generated images for your users.
|
3 |
+
|
4 |
+
Key features:
|
5 |
+
- 🤗 Sign in with Hugging Face
|
6 |
+
- Save generated images with their metadata: prompts, timestamp, hyper-parameters, etc.
|
7 |
+
- Export your history as zip.
|
8 |
+
- Delete your history to respect privacy.
|
9 |
+
- Compatible with Persistent Storage for long-term storage.
|
10 |
+
- Admin panel to check configuration and disk usage .
|
11 |
+
|
12 |
+
Useful links:
|
13 |
+
- Demo: https://huggingface.co/spaces/Wauplin/gradio-user-history
|
14 |
+
- README: https://huggingface.co/spaces/Wauplin/gradio-user-history/blob/main/README.md
|
15 |
+
- Source file: https://huggingface.co/spaces/Wauplin/gradio-user-history/blob/main/user_history.py
|
16 |
+
- Discussions: https://huggingface.co/spaces/Wauplin/gradio-user-history/discussions
|
17 |
+
"""
|
18 |
import json
|
19 |
import os
|
20 |
import shutil
|
|
|
37 |
user_history.folder_path = _resolve_folder_path(folder_path)
|
38 |
user_history.initialized = True
|
39 |
|
40 |
+
# TODO: remove this section once all Spaces have migrated
|
41 |
+
_migrate_history()
|
42 |
+
|
43 |
|
44 |
def render() -> None:
|
45 |
user_history = _UserHistory()
|
|
|
66 |
with gr.Row():
|
67 |
gr.LoginButton(min_width=250)
|
68 |
gr.LogoutButton(min_width=250)
|
69 |
+
refresh_button = gr.Button(
|
70 |
+
"Refresh",
|
71 |
+
icon="https://huggingface.co/spaces/Wauplin/gradio-user-history/resolve/main/assets/icon_refresh.png",
|
72 |
+
)
|
73 |
+
export_button = gr.Button(
|
74 |
+
"Export",
|
75 |
+
icon="https://huggingface.co/spaces/Wauplin/gradio-user-history/resolve/main/assets/icon_download.png",
|
76 |
+
)
|
77 |
+
delete_button = gr.Button(
|
78 |
+
"Delete history",
|
79 |
+
icon="https://huggingface.co/spaces/Wauplin/gradio-user-history/resolve/main/assets/icon_delete.png",
|
80 |
+
)
|
81 |
|
82 |
# "Export zip" row (hidden by default)
|
83 |
with gr.Row():
|
|
|
424 |
if response.status_code == 200:
|
425 |
return sorted((member["user"] for member in response.json()), key=lambda x: x.lower())
|
426 |
return [namespace]
|
427 |
+
|
428 |
+
|
429 |
+
################################################################
|
430 |
+
# Legacy helpers to migrate image structure to new data format #
|
431 |
+
################################################################
|
432 |
+
# TODO: remove this section once all Spaces have migrated
|
433 |
+
|
434 |
+
|
435 |
+
def _migrate_history():
|
436 |
+
"""Script to migrate user history from v0 to v1."""
|
437 |
+
legacy_history_path = _legacy_get_history_folder_path()
|
438 |
+
if not legacy_history_path.exists():
|
439 |
+
return
|
440 |
+
|
441 |
+
error_count = 0
|
442 |
+
for json_path in legacy_history_path.glob("*.json"):
|
443 |
+
username = json_path.stem
|
444 |
+
print(f"Migrating history for user {username}...")
|
445 |
+
error_count += _legacy_move_user_history(username)
|
446 |
+
print("Done.")
|
447 |
+
print(f"Migration complete. {error_count} error(s) happened.")
|
448 |
+
|
449 |
+
if error_count == 0:
|
450 |
+
shutil.rmtree(legacy_history_path, ignore_errors=True)
|
451 |
+
|
452 |
+
|
453 |
+
def _legacy_move_user_history(username: str) -> int:
|
454 |
+
history = _legacy_read_user_history(username)
|
455 |
+
error_count = 0
|
456 |
+
for image, prompt in reversed(history):
|
457 |
+
try:
|
458 |
+
save_image(label=prompt, image=image, profile={"preferred_username": username})
|
459 |
+
except Exception as e:
|
460 |
+
print("Issue while migrating image:", e)
|
461 |
+
error_count += 1
|
462 |
+
return error_count
|
463 |
+
|
464 |
+
|
465 |
+
def _legacy_get_history_folder_path() -> Path:
|
466 |
+
_folder = os.environ.get("HISTORY_FOLDER")
|
467 |
+
if _folder is None:
|
468 |
+
_folder = Path(__file__).parent / "history"
|
469 |
+
return Path(_folder)
|
470 |
+
|
471 |
+
|
472 |
+
def _legacy_read_user_history(username: str) -> List[Tuple[str, str]]:
|
473 |
+
"""Return saved history for that user."""
|
474 |
+
with _legacy_user_lock(username):
|
475 |
+
path = _legacy_user_history_path(username)
|
476 |
+
if path.exists():
|
477 |
+
return json.loads(path.read_text())
|
478 |
+
return [] # No history yet
|
479 |
+
|
480 |
+
|
481 |
+
def _legacy_user_history_path(username: str) -> Path:
|
482 |
+
return _legacy_get_history_folder_path() / f"{username}.json"
|
483 |
+
|
484 |
+
|
485 |
+
def _legacy_user_lock(username: str) -> FileLock:
|
486 |
+
"""Ensure history is not corrupted if concurrent calls."""
|
487 |
+
return FileLock(f"{_legacy_user_history_path(username)}.lock")
|