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:
pngwn 2022-11-15 22:23:47 +01:00 committed by GitHub
parent e6336d6882
commit 4b57984ead
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 91 additions and 24 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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,
):

View File

@ -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": {},
},
},

View File

@ -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",

View File

@ -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):

View File

@ -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

View File

@ -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"