Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation #2600

Merged
merged 26 commits into from
May 25, 2023
Merged

Validation #2600

merged 26 commits into from
May 25, 2023

Conversation

darrenburns
Copy link
Member

@darrenburns darrenburns commented May 17, 2023

Here's an example which covers lots of the new additions (video and code below):

  • The validation framework
  • Using multiple validators with a widget
  • Writing a custom validator (Palindrome)
  • Handling validation successes and failures and updating the UI correspondingly.
  • Customising error messages ("That's not a palindrome :/")
  • The -valid and -invalid styles applied to Input. Note that only -invalid has styling by default. -valid has no default styling - but we attach it anyway so that users have the flexibility to change styling when validation explicitly passes. In the example below, when validation passes, the border of the Input goes green.
validation-23may23.mov
from textual import on
from textual.app import App, ComposeResult
from textual.validation import Function, Number, ValidationResult, Validator
from textual.widgets import Input, Label, Pretty


class InputApp(App):
    CSS = """
    Input.-valid {
        border: tall $success 60%;
    }
    Input.-valid:focus {
        border: tall $success;
    }
    Input {
        margin: 1 1;
    }
    Label {
        margin: 1 2;
    }
    Pretty {
        margin: 1 2;
    }
    """

    def compose(self) -> ComposeResult:
        yield Label("Enter an even number between 1 and 100 that is also a palindrome.")
        yield Input(
            placeholder="Enter a number...",
            validators=[
                Number(minimum=1, maximum=100),
                Function(is_even, "Value is not even."),
                Palindrome(),
            ],
        )
        yield Pretty([])

    @on(Input.Changed)
    def show_invalid_reasons(self, event: Input.Changed) -> None:
        # Updating the UI to show the reasons why validation failed
        if not event.validation_result:
            self.query_one(Pretty).update(event.validation_result.failure_descriptions)
        else:
            self.query_one(Pretty).update([])


def is_even(value: str) -> bool:
    try:
        return int(value) % 2 == 0
    except ValueError:
        return False


# A custom validator
class Palindrome(Validator):
    def validate(self, value: str) -> ValidationResult:
        """Check a string is equal to its reverse."""
        if self.is_palindrome(value):
            return self.success()
        else:
            return self.failure("That's not a palindrome :/")

    @staticmethod
    def is_palindrome(value: str) -> bool:
        return value == value[::-1]


app = InputApp()

if __name__ == "__main__":
    app.run()

Copy link
Collaborator

@willmcgugan willmcgugan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some comments. Will do a review tomorrow.

src/textual/validation.py Outdated Show resolved Hide resolved
src/textual/validation.py Outdated Show resolved Hide resolved
src/textual/validation.py Outdated Show resolved Hide resolved
src/textual/widgets/_input.py Show resolved Hide resolved
@darrenburns darrenburns marked this pull request as ready for review May 24, 2023 10:04
Copy link
Collaborator

@willmcgugan willmcgugan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work. Some comments, and maybe-issues.

docs/widgets/input.md Outdated Show resolved Hide resolved
src/textual/validation.py Show resolved Hide resolved
src/textual/validation.py Show resolved Hide resolved

def __post_init__(self) -> None:
# If a failure message isn't supplied, try to get it from the Validator.
if self.description is None and self.validator is not None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.validator can never be None according the the type.

I gather this sets description if its None. But that means that description is still type as being | None, which it never will be from the POV of the Textual developer.

Can this logic be moved to where a Failure is constructed? Thus removing the need to type description as potentially being None ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is possible without having to duplicate this logic across all validators (meaning users writing custom validators would also have to duplicate this logic). I think things will become much messier and the trade-off isn't worth it unless you can suggest another approach.

Is the description being None bad? Maybe as a developer who wants a custom Validator, I just have no need to describe my validation failure because I won't be using the description anyway.

src/textual/validation.py Show resolved Hide resolved
src/textual/validation.py Show resolved Hide resolved
src/textual/validation.py Outdated Show resolved Hide resolved
@darrenburns darrenburns requested a review from willmcgugan May 24, 2023 12:14
Copy link
Collaborator

@willmcgugan willmcgugan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some requests re error messages

src/textual/validation.py Outdated Show resolved Hide resolved
src/textual/validation.py Show resolved Hide resolved
src/textual/validation.py Outdated Show resolved Hide resolved
@darrenburns darrenburns requested a review from willmcgugan May 25, 2023 12:16
Copy link
Collaborator

@willmcgugan willmcgugan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@willmcgugan willmcgugan merged commit 62fcefb into main May 25, 2023
@willmcgugan willmcgugan deleted the validation branch May 25, 2023 12:29
@darrenburns darrenburns mentioned this pull request May 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants