Spaces:
Running on Zero
Running on Zero
CI deploy b5b8ab30
Browse files- app.py +9 -10
- tests/test_ui.py +41 -0
app.py
CHANGED
|
@@ -764,16 +764,15 @@ def build_app() -> gr.Blocks:
|
|
| 764 |
# is exactly what broke the UI while leaving the API working.
|
| 765 |
demo.load(fn=None, inputs=None, outputs=None, js=BRIDGE_LOAD_JS)
|
| 766 |
|
| 767 |
-
#
|
| 768 |
-
#
|
| 769 |
-
#
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
demo.load(whoami, [state], [state, top_html])
|
| 777 |
|
| 778 |
return demo
|
| 779 |
|
|
|
|
| 764 |
# is exactly what broke the UI while leaving the API working.
|
| 765 |
demo.load(fn=None, inputs=None, outputs=None, js=BRIDGE_LOAD_JS)
|
| 766 |
|
| 767 |
+
# No OAuth-annotated `demo.load` here, deliberately. A handler that
|
| 768 |
+
# takes `gr.OAuthProfile` asks Gradio for an authenticated session; on
|
| 769 |
+
# a *load* event that fires every render, an unauthenticated visitor is
|
| 770 |
+
# sent to sign in, comes back, fires load again, and is sent to sign in
|
| 771 |
+
# again -- the browser reports it as "redirected you too many times".
|
| 772 |
+
#
|
| 773 |
+
# `gr.LoginButton` already shows signed-in state on its own, and the
|
| 774 |
+
# profile is read where it is actually needed: the user-initiated
|
| 775 |
+
# extension handlers in src/extension.py.
|
|
|
|
| 776 |
|
| 777 |
return demo
|
| 778 |
|
tests/test_ui.py
CHANGED
|
@@ -736,3 +736,44 @@ def test_bridge_js_is_loaded_without_outputs():
|
|
| 736 |
|
| 737 |
src = inspect.getsource(bitapp.build_app)
|
| 738 |
assert "demo.load(fn=None, inputs=None, outputs=None, js=BRIDGE_LOAD_JS)" in src
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 736 |
|
| 737 |
src = inspect.getsource(bitapp.build_app)
|
| 738 |
assert "demo.load(fn=None, inputs=None, outputs=None, js=BRIDGE_LOAD_JS)" in src
|
| 739 |
+
|
| 740 |
+
|
| 741 |
+
def test_no_oauth_annotated_load_handler():
|
| 742 |
+
"""A `demo.load` handler that takes `gr.OAuthProfile` asks for an
|
| 743 |
+
authenticated session on every page render. An unauthenticated visitor is
|
| 744 |
+
sent to sign in, returns, fires load again, and is sent back -- an infinite
|
| 745 |
+
redirect. The profile belongs on user-initiated handlers only."""
|
| 746 |
+
import inspect
|
| 747 |
+
import re
|
| 748 |
+
|
| 749 |
+
src = inspect.getsource(bitapp.build_app)
|
| 750 |
+
# Strip comments so the explanation of this rule does not trip it.
|
| 751 |
+
code = "\n".join(ln for ln in src.splitlines()
|
| 752 |
+
if not ln.lstrip().startswith("#"))
|
| 753 |
+
assert "OAuthProfile" not in code, \
|
| 754 |
+
"an OAuth-annotated handler is registered in build_app"
|
| 755 |
+
|
| 756 |
+
for call in re.findall(r"demo\.load\((.*?)\)\n", code, re.S):
|
| 757 |
+
assert "profile" not in call, f"load handler takes a profile: {call[:80]}"
|
| 758 |
+
|
| 759 |
+
|
| 760 |
+
def test_profile_is_read_only_on_user_initiated_handlers():
|
| 761 |
+
"""Where the profile *is* needed, it must hang off a click, not a load."""
|
| 762 |
+
import inspect
|
| 763 |
+
|
| 764 |
+
from src import extension
|
| 765 |
+
|
| 766 |
+
for fn in (extension.extend_ui, extension.add_model_ui):
|
| 767 |
+
params = inspect.signature(fn).parameters
|
| 768 |
+
assert "profile" in params, f"{fn.__name__} should receive the profile"
|
| 769 |
+
|
| 770 |
+
|
| 771 |
+
def test_login_button_is_the_only_sign_in_mechanism():
|
| 772 |
+
import inspect
|
| 773 |
+
|
| 774 |
+
from src.ui import shell
|
| 775 |
+
|
| 776 |
+
src = inspect.getsource(bitapp.build_app)
|
| 777 |
+
assert "gr.LoginButton" in src
|
| 778 |
+
# The header must not also hand-roll a link to the OAuth route.
|
| 779 |
+
assert "/login/huggingface" not in shell.top_bar(on_space=True)
|