mirror of
https://github.com/gradio-app/gradio.git
synced 2024-11-21 01:01:05 +08:00
add password and email textbox types (#2653)
* add password and email textbox types * changelog + fix tests * formatting * fix tests maybe * fix tests * add more tests * whatever * weird thing
This commit is contained in:
parent
e6336d6882
commit
4b57984ead
@ -1,7 +1,7 @@
|
||||
# Upcoming Release
|
||||
|
||||
## New Features:
|
||||
No changes to highlight.
|
||||
* Add support for `'password'` and `'email'` types to `Textbox`. [@pngwn](https://github.com/pngwn) in [PR 2653](https://github.com/gradio-app/gradio/pull/2653)
|
||||
|
||||
## Bug Fixes:
|
||||
* Updated the minimum FastApi used in tests to version 0.87 [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2647](https://github.com/gradio-app/gradio/pull/2647)
|
||||
|
@ -1,36 +1,46 @@
|
||||
import gradio as gr
|
||||
import os
|
||||
|
||||
|
||||
def combine(a, b):
|
||||
return a + " " + b
|
||||
|
||||
|
||||
def mirror(x):
|
||||
return x
|
||||
|
||||
|
||||
with gr.Blocks() as demo:
|
||||
|
||||
|
||||
txt = gr.Textbox(label="Input", lines=2)
|
||||
txt_2 = gr.Textbox(label="Input 2")
|
||||
txt_3 = gr.Textbox(value="", label="Output")
|
||||
btn = gr.Button(value="Submit")
|
||||
btn.click(combine, inputs=[txt, txt_2], outputs=[txt_3])
|
||||
|
||||
|
||||
with gr.Row():
|
||||
im = gr.Image()
|
||||
im_2 = gr.Image()
|
||||
|
||||
|
||||
btn = gr.Button(value="Mirror Image")
|
||||
btn.click(mirror, inputs=[im], outputs=[im_2])
|
||||
|
||||
|
||||
gr.Markdown("## Text Examples")
|
||||
gr.Examples([["hi", "Adam"], ["hello", "Eve"]], [txt, txt_2], txt_3, combine, cache_examples=True)
|
||||
gr.Examples(
|
||||
[["hi", "Adam"], ["hello", "Eve"]],
|
||||
[txt, txt_2],
|
||||
txt_3,
|
||||
combine,
|
||||
cache_examples=True,
|
||||
)
|
||||
gr.Markdown("## Image Examples")
|
||||
gr.Examples(
|
||||
examples=[os.path.join(os.path.dirname(__file__), "lion.jpg")],
|
||||
inputs=im,
|
||||
outputs=im_2,
|
||||
fn=mirror,
|
||||
cache_examples=True)
|
||||
cache_examples=True,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo.launch()
|
||||
|
@ -281,6 +281,7 @@ class Textbox(
|
||||
interactive: Optional[bool] = None,
|
||||
visible: bool = True,
|
||||
elem_id: Optional[str] = None,
|
||||
type: str = "text",
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
@ -294,9 +295,14 @@ class Textbox(
|
||||
interactive: if True, will be rendered as an editable textbox; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
|
||||
visible: If False, component will be hidden.
|
||||
elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
|
||||
type: The type of textbox. One of: 'text', 'password', 'email', Default is 'text'.
|
||||
"""
|
||||
if type not in ["text", "password", "email"]:
|
||||
raise ValueError('`type` must be one of "text", "password", or "email".')
|
||||
|
||||
#
|
||||
self.lines = lines
|
||||
self.max_lines = max_lines
|
||||
self.max_lines = max_lines if type == "text" else 1
|
||||
self.placeholder = placeholder
|
||||
self.interpret_by_tokens = True
|
||||
IOComponent.__init__(
|
||||
@ -311,6 +317,7 @@ class Textbox(
|
||||
)
|
||||
self.cleared_value = ""
|
||||
self.test_input = value
|
||||
self.type = type
|
||||
|
||||
def get_config(self):
|
||||
return {
|
||||
@ -318,6 +325,7 @@ class Textbox(
|
||||
"max_lines": self.max_lines,
|
||||
"placeholder": self.placeholder,
|
||||
"value": self.value,
|
||||
"type": self.type,
|
||||
**IOComponent.get_config(self),
|
||||
}
|
||||
|
||||
@ -331,6 +339,7 @@ class Textbox(
|
||||
show_label: Optional[bool] = None,
|
||||
visible: Optional[bool] = None,
|
||||
interactive: Optional[bool] = None,
|
||||
type: Optional[str] = None,
|
||||
):
|
||||
updated_config = {
|
||||
"lines": lines,
|
||||
@ -340,6 +349,7 @@ class Textbox(
|
||||
"show_label": show_label,
|
||||
"visible": visible,
|
||||
"value": value,
|
||||
"type": type,
|
||||
"__type__": "update",
|
||||
}
|
||||
return IOComponent.add_interactive_to_config(updated_config, interactive)
|
||||
|
@ -19,7 +19,7 @@ class Textbox(components.Textbox):
|
||||
placeholder: Optional[str] = None,
|
||||
default: str = "",
|
||||
numeric: Optional[bool] = False,
|
||||
type: Optional[str] = "str",
|
||||
type: Optional[str] = "text",
|
||||
label: Optional[str] = None,
|
||||
optional: bool = False,
|
||||
):
|
||||
|
@ -126,6 +126,7 @@ XRAY_CONFIG = {
|
||||
"lines": 1,
|
||||
"max_lines": 20,
|
||||
"value": "",
|
||||
"type": "text",
|
||||
"show_label": True,
|
||||
"name": "textbox",
|
||||
"visible": True,
|
||||
@ -358,6 +359,7 @@ XRAY_CONFIG_DIFF_IDS = {
|
||||
"lines": 1,
|
||||
"max_lines": 20,
|
||||
"value": "",
|
||||
"type": "text",
|
||||
"show_label": True,
|
||||
"name": "textbox",
|
||||
"visible": True,
|
||||
@ -593,6 +595,7 @@ XRAY_CONFIG_WITH_MISTAKE = {
|
||||
"lines": 1,
|
||||
"value": "",
|
||||
"name": "textbox",
|
||||
"type": "text",
|
||||
"style": {},
|
||||
},
|
||||
},
|
||||
|
@ -714,6 +714,8 @@ class TestSpecificUpdate:
|
||||
"placeholder": None,
|
||||
"label": None,
|
||||
"show_label": None,
|
||||
"type": None,
|
||||
"type": None,
|
||||
"visible": None,
|
||||
"value": gr.components._Keywords.NO_VALUE,
|
||||
"__type__": "update",
|
||||
@ -729,6 +731,7 @@ class TestSpecificUpdate:
|
||||
"placeholder": None,
|
||||
"label": None,
|
||||
"show_label": None,
|
||||
"type": None,
|
||||
"visible": None,
|
||||
"value": gr.components._Keywords.NO_VALUE,
|
||||
"__type__": "update",
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""
|
||||
Tests for all of the componets defined in components.py. Tests are divided into two types:
|
||||
Tests for all of the components defined in components.py. Tests are divided into two types:
|
||||
1. test_component_functions() are unit tests that check essential functions of a component, the functions that are checked are documented in the docstring.
|
||||
2. test_in_interface() are functional tests that check a component's functionalities inside an Interface. Please do not use Interface.launch() in this file, as it slow downs the tests.
|
||||
"""
|
||||
@ -59,9 +59,6 @@ class TestTextbox:
|
||||
assert text_input.postprocess(2.14) == "2.14"
|
||||
assert text_input.serialize("Hello World!", True) == "Hello World!"
|
||||
|
||||
with pytest.warns(Warning):
|
||||
_ = gr.Textbox(type="number")
|
||||
|
||||
assert text_input.tokenize("Hello World! Gradio speaking.") == (
|
||||
["Hello", "World!", "Gradio", "speaking."],
|
||||
[
|
||||
@ -90,6 +87,7 @@ class TestTextbox:
|
||||
"value": "",
|
||||
"name": "textbox",
|
||||
"show_label": True,
|
||||
"type": "text",
|
||||
"label": None,
|
||||
"style": {},
|
||||
"elem_id": None,
|
||||
@ -166,6 +164,18 @@ class TestTextbox:
|
||||
assert component.get_config().get("value") == "abc"
|
||||
assert component.get_config().get("lines") == 4
|
||||
|
||||
def test_faulty_type(self):
|
||||
with pytest.raises(
|
||||
ValueError, match='`type` must be one of "text", "password", or "email".'
|
||||
):
|
||||
gr.Textbox(type="boo")
|
||||
|
||||
def test_max_lines(self):
|
||||
assert gr.Textbox(type="password").get_config().get("max_lines") == 1
|
||||
assert gr.Textbox(type="email").get_config().get("max_lines") == 1
|
||||
assert gr.Textbox(type="text").get_config().get("max_lines") == 20
|
||||
assert gr.Textbox().get_config().get("max_lines") == 20
|
||||
|
||||
|
||||
class TestNumber:
|
||||
def test_component_functions(self):
|
||||
|
@ -15,6 +15,7 @@
|
||||
export let placeholder: string = "";
|
||||
export let show_label: boolean;
|
||||
export let max_lines: number | false;
|
||||
export let type: "text" | "password" | "email" = "text";
|
||||
|
||||
export let style: Styles = {};
|
||||
|
||||
@ -35,6 +36,7 @@
|
||||
{label}
|
||||
{show_label}
|
||||
{lines}
|
||||
{type}
|
||||
max_lines={!max_lines && mode === "static" ? lines + 1 : max_lines}
|
||||
{placeholder}
|
||||
on:change
|
||||
|
@ -9,6 +9,7 @@
|
||||
export let disabled = false;
|
||||
export let show_label: boolean = true;
|
||||
export let max_lines: number | false;
|
||||
export let type: "text" | "password" | "email" = "text";
|
||||
|
||||
let el: HTMLTextAreaElement | HTMLInputElement;
|
||||
|
||||
@ -93,17 +94,45 @@
|
||||
<BlockTitle {show_label}>{label}</BlockTitle>
|
||||
|
||||
{#if lines === 1 && max_lines === 1}
|
||||
<input
|
||||
data-testid="textbox"
|
||||
type="text"
|
||||
class="scroll-hide block gr-box gr-input w-full gr-text-input"
|
||||
bind:value
|
||||
bind:this={el}
|
||||
{placeholder}
|
||||
{disabled}
|
||||
on:keypress={handle_keypress}
|
||||
on:blur={handle_blur}
|
||||
/>
|
||||
{#if type === "text"}
|
||||
<input
|
||||
data-testid="textbox"
|
||||
type="text"
|
||||
class="scroll-hide block gr-box gr-input w-full gr-text-input"
|
||||
bind:value
|
||||
bind:this={el}
|
||||
{placeholder}
|
||||
{disabled}
|
||||
on:keypress={handle_keypress}
|
||||
on:blur={handle_blur}
|
||||
/>
|
||||
{:else if type === "password"}
|
||||
<input
|
||||
data-testid="password"
|
||||
type="password"
|
||||
class="scroll-hide block gr-box gr-input w-full gr-text-input"
|
||||
bind:value
|
||||
bind:this={el}
|
||||
{placeholder}
|
||||
{disabled}
|
||||
on:keypress={handle_keypress}
|
||||
on:blur={handle_blur}
|
||||
autocomplete=""
|
||||
/>
|
||||
{:else if type === "email"}
|
||||
<input
|
||||
data-testid="textbox"
|
||||
type="email"
|
||||
class="scroll-hide block gr-box gr-input w-full gr-text-input"
|
||||
bind:value
|
||||
bind:this={el}
|
||||
{placeholder}
|
||||
{disabled}
|
||||
on:keypress={handle_keypress}
|
||||
on:blur={handle_blur}
|
||||
autocomplete="email"
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<textarea
|
||||
data-testid="textbox"
|
||||
|
Loading…
Reference in New Issue
Block a user