hf_bouncer_V2 / app.py
JustAnotherCibrarian's picture
Revert to V2,
781ae19 verified
import os
import gradio as gr
from typing import Optional
from huggingface_hub import HfApi, list_models
DUMMY_REFRESH = "Load data from user profile" # Selecting this entry will trigger refresh all.
isdummy = lambda name, dummy = DUMMY_REFRESH: name == dummy or dummy in name
WILD_REFRESH = "**"
iswild = lambda name, wild = WILD_REFRESH: name == wild or wild in name
MYSPACE = os.getenv("SPACE_ID", "")
MYUSER = MYSPACE.split("/")[0]
api = HfApi()
def get_user_repos(username: str, token: str) -> list[str]:
"""Fetch all repos under a user's namespace."""
# api = HfApi(token=token)
# repos = api.list_repos_objs(author=username)
repos = list_models(author=username)
# repos = [repo.id.split("/", 1)[1] for repo in repos] # Actually need authorid for exists to work.
repos = sorted([repo.id for repo in repos]) # author/repo.
return repos
def get_gated_repos(username: str, token: str) -> list[str]:
"""Fetch all gated repos."""
repos = list_models(author=username, gated = True)
# repos = [repo.id.split("/", 1)[1] for repo in repos] # Actually need authorid for exists to work.
repos = sorted([repo.id for repo in repos]) # author/repo.
return repos
def get_organisation(username: str) -> list[str]:
"""Returns all members in organisation.
If not an organisation, returns [user].
"""
try:
return [u.username for u in api.list_organization_members(username)]
except Exception as e:
if isinstance(username, list):
return username
else:
return [username]
def grant_access_to_repo(token, repo_id, username):
"""
Grant access to a user/organization for a specific repo
"""
try:
api.grant_access(repo_id=repo_id, user=username, token=token)
return f"✅ {repo_id} | {username}: Granted."
except Exception as e:
if "already has access to the repo" in str(e):
return f"🔓 {repo_id} | {username}: Already has access."
return f"❌ {repo_id} | {username}: Error. {str(e)}"
def grant_access_multiple_repos(token, repo_list, username):
"""Grant access to multiple repositories + users at once.
Users are dereferenced by organisation.
"""
if not token:
return "ERROR: Cannot grant access without a write token."
if not repo_list:
return "ERROR: No repos selected."
if not username:
return "ERROR: No user selected."
if not isinstance(repo_list, list):
repo_list = [repo_list]
lusers = get_organisation(username)
results = []
for user in lusers:
for repo_id in repo_list:
results.append(grant_access_to_repo(token, repo_id, user))
return "\n".join(results)
with gr.Blocks(title="HF Bouncer") as demo:
gr.Markdown("# ⛹️ Huggingface Bouncer")
gr.Markdown("Grant access to users/organizations for multiple repositories")
hf_user = gr.Textbox(
label = "Huggingface user",
value = MYUSER,
info = "User / org whose repos are granted.",
max_lines = 1,
)
hf_token = gr.Textbox(
label="Huggingface token",
placeholder="hf_xxxxxxxxxxxxxxxx",
type="password",
info="Your HF token with write permissions",
max_lines=1
)
refresh_repos_btn = gr.Button( # Press this button to get external user repo data, after login.
"🔄 Refresh external data - presets and duplicate repos",
variant="secondary",
scale=0,
min_width=40
)
with gr.Row():
with gr.Column():
multi_repo_input = gr.Dropdown(
label="Repositories to grant",
multiselect=True,
allow_custom_value=True, # Can type other people's repos.
interactive=True,
choices=[DUMMY_REFRESH],
value=[],
info="Username/repo-name"
)
multi_user_input = gr.Textbox(
label="User/Organization",
placeholder="username-or-org-name"
)
multi_btn = gr.Button("Grant Access to All", variant="primary")
with gr.Column():
multi_output = gr.Textbox(label="Results", lines=10)
# ---Actions---
# Refresh external data.
def refresh_all_data(username, token, force = False, indwild = False):
try:
# Doesn't matter, listing works without tokens.
# if not token:
# raise ValueError("Please login first!")
# Refresh repos
lrepos = get_gated_repos(username, token)
# Add wildcard at the top.
lreposw = [WILD_REFRESH] + lrepos
# Check if wildcard selected, select all values (besides wc).
lsel = []
if indwild: # Wildcard selected,
lsel = lrepos
return gr.Dropdown(choices=lreposw, value=lsel)
except Exception as e:
gr.Warning(f"Failed to refresh data: {str(e)}")
return gr.Dropdown(choices=[DUMMY_REFRESH], value=[])
def force_refresh(username, token):
"""Run refresh with force.
Lambda method doesn't seem to work for some reason,
perhaps profile and oauth aren't functional with args/kwargs.
"""
return refresh_all_data(username, token, force = True)
refresh_repos_btn.click(
fn=force_refresh,
inputs=[hf_user, hf_token],
outputs=[multi_repo_input]
)
def check_dummy(username, token, dropdown_value = None):
"""If dummy was selected, initiate dropdown refresh.
"""
name = ""
if not dropdown_value: # Empty list or null.
pass
elif isinstance(dropdown_value, "".__class__): # Single name.
name = dropdown_value
# elif len(dropdown_value) > 0: # Multilist, we only care about one item.
# name = dropdown_value[0]
else:
name = dropdown_value
if isdummy(name):
return refresh_all_data(username, token)
if iswild(name):
return refresh_all_data(username, token, indwild = True)
return gr.Dropdown()
# Dummy load only.
multi_repo_input.change(
fn=check_dummy,
inputs=[hf_user, hf_token, multi_repo_input],
outputs=[multi_repo_input]
)
multi_btn.click(
fn=grant_access_multiple_repos,
inputs=[hf_token, multi_repo_input, multi_user_input],
outputs=multi_output
)
if __name__ == "__main__":
demo.launch()