I have noticed that prompt using click accepts inputs with trailing spaces
ftp_server = click.prompt("FTP Server")
Is there a way to use a custom return function like this to reject trailing spaces?
def custom_prompt(value):
if value.strip():
return True
else:
return False
ftp_server = click.prompt("FTP Server", custom_prompt)
I have already used this:
while not ftp_server.strip():
ftp_server = click.prompt("FTP Server")
But I'm looking for a better way because I don't want to use a while loop each time I use prompt.
To reject invalid user input, you can use the value_proc parameter to click.prompt. A validator to reject input with trailing spaces could look like:
Prompt Validator
import click
def validate_no_trailing_space(value):
if value != value.rstrip():
raise click.UsageError("Trailing space is invalid!")
return value
ftp_server = click.prompt("FTP Server",
value_proc=validate_no_trailing_space)
Trim Spaces
You might also consider a validator which trims leading and trailing spaces but reject spaces in the name:
def validate_no_internal_space(value):
value = value.strip()
if ' ' in value:
raise click.UsageError("Spaces are not valid here!")
return value
Related
#Want this code but to only allow str (alphabet)
def is_type_int(*args):
item = var2.get()
try:
item_type = type(int(item))
if item_type == type(int(1)):
#Had too have any action here so just made a print do nothing
print
except:
IDEntry.delete(0, tk.END)
var2 = tk.StringVar()
var2.trace("w", is_type_int)
So, the first option I was thinking about it to compare the type to str instead of int. But this solution would not always work because it depends on how the argument is passed.
I'd try to use regex instead, use a simple function to check if there is any number in the input text as below:
import re
def numberInText(input_string):
# Using regex, look for a number in the string
if (re.search("[0-9]",input_string)):
return True
# if no number is found, return False
return False
test_with_number = "TEST 123"
print(numberInText(test_with_number))
test_without_number = "TEST"
print(numberInText(test_without_number))
I have 3 fields that I want to compare salary "from" field and "to" field and also there is fixed salary field. I have no idea how to do it, since there is no documentation how to do it, so i created custom function that look to each other and trying to se if they have a value.
def validate_salarylow(self, salarylow):
if self.validate_salary_fixed(self.salary_fixed) != "":
salarylow.data = int(0)
else:
try:
salarylow.data = int(salarylow.data)
except:
raise ValidationError("value is not a number")
return salarylow.data
def validate_salary_high(self, salary_high):
if self.validate_salary_fixed(self.salary_fixed) != "":
salary_high.data = int(0)
else:
try:
salary_high.data = int(salary_high.data)
except:
raise ValidationError("value is not a number")
return salary_high.data
def validate_salary_fixed(self, salary_fixed):
if self.validate_salary_high(self.salary_high) != "":
salary_fixed.data = int(0)
try:
salary_fixed.data = int(salary_fixed.data)
except:
raise ValidationError("value is not a number")
return salary_fixed.data
if I don't set if self.validate_salary_high(self.salary_high) != "": everything works fine. but when i set it I'm getting "RecursionError: maximum recursion depth exceeded" error.validate_salary_fixed function looks to validate_salary_high function and vice versa. I'm new in Python and flask and I'm sure there is easy solution, but I cant find it so I would appreciate if anyone could help.
My suggestion is to suppress the error message of the integer field by overwriting it. Thus, the types of the inputs do not have to be converted.
For validation I use two custom validators, one of which checks whether a range or a fixed value has been entered and the second checks the range for its limits. In addition, pre-built validators are used to prohibit negative values.
I'm not sure if you really need the field for the fixed salary, because it is possible to define a fixed value by narrowing the range.
from flask_wtf import FlaskForm
from wtforms import IntegerField
from wtforms.validators import (
NumberRange,
Optional,
StopValidation,
ValidationError
)
class OptionalIntegerField(IntegerField):
def process_data(self, value):
try:
super().process_data(value)
except ValueError:
pass
def process_formdata(self, valuelist):
try:
super().process_formdata(valuelist)
except ValueError:
pass
def validate_salary(form, field):
range_fields = [form.salary_low, form.salary_high]
if all(f.data is None for f in [form.salary_low, form.salary_high, form.salary_fixed]) or \
(form.salary_fixed.data is not None and any(f.data is not None for f in range_fields)) or \
(form.salary_fixed.data is None and any(f.data is None for f in range_fields)):
raise StopValidation('Either state a range from low to high or a fixed salary.')
def validate_salary_range(form, field):
if form.salary_low.data and form.salary_high.data and \
form.salary_low.data > form.salary_high.data:
raise ValidationError('The lower value should be less than or equal to the higher one.')
class SalaryForm(FlaskForm):
salary_low = OptionalIntegerField(
validators=[
validate_salary,
validate_salary_range,
Optional(),
NumberRange(min=0)
]
)
salary_high = OptionalIntegerField(
validators=[
validate_salary,
validate_salary_range,
Optional(),
NumberRange(min=0)
]
)
salary_fixed = OptionalIntegerField(
validators=[
validate_salary,
Optional(),
NumberRange(min=0)
]
)
app = Flask(__name__)
app.secret_key = 'your secret here'
#app.route('/', methods=['GET', 'POST'])
def index():
form = SalaryForm(request.form)
if form.validate_on_submit():
print(form.salary_low.data, ' - ', form.salary_high.data, '||', form.salary_fixed.data)
return render_template('index.html', **locals())
Let's take a look at your code:
Your function validate_salary_high calls validate_salary_fixed.
But when you go to your function validate_salary_fixed it calls validate_salary_high.
So you go back to your function validate_salary_high which calls validate_salary_fixed.
Now in your function validate_salary_fixed, you call validate_salary_high.
Your functions repeatedly call each other over and over again, forever, until your computer eventually throws an error - and this is exactly what is happening to you.
The way to get around this is to remove one of your recursive calls. More specifically you should either
remove your call to validate_salary_fixed in the function validate_salary_high
or remove your call to validate_salary_high in the function validate_salary_fixed
You should chose which function call to remove depending on the goal of your code (which I don't fully understand.) Good luck!
I am in need of a function that retricts the input of the user to numeric values.
I've been looking for an answer but the one I've found does not allow for '-', '+' and ','(comma).
Here's the code for the validation method:
def __init__(self, master1):
self.panel2 = tk.Frame(master1)
self.panel2.grid()
vcmd = (master1.register(self.validate),
'%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
self.text1 = tk.Entry(self.panel2, validate = 'key', validatecommand = vcmd)
self.text1.grid()
self.text1.focus()
def validate(self, action, index, value_if_allowed,
prior_value, text, validation_type, trigger_type, widget_name):
# action=1 -> insert
if(action=='1'):
if text in '0123456789,-+':
try:
float(value_if_allowed)
return True
except ValueError:
return False
else:
return False
else:
return True
Again, this only seems to work with numbers, but restricts commas and plus and minus signs, which is not intended.
How could this be fixed?
The right tool is definitely the re module.
Here is a regular expression that should do the job:
(\+|\-)?\d+(,\d+)?$
Let's break it down:
(\+|\-)?\d+(,\d+)?$
\+|\- Starts with a + or a -...
( )? ... but not necessarily
\d+ Contains a repetition of at least one digits
,\d+ Is followed by a comma and at least one digits...
( )? ... but not necessarily
$ Stops here: nothing follows the trailing digits
Now, your validate function only has to return True if the input matches that regex, and False if it does not.
def validate(string):
result = re.match(r"(\+|\-)?\d+(,\d+)?$", string)
return result is not None
Some tests:
# Valid inputs
>>> validate("123")
True
>>> validate("123,2")
True
>>> validate("+123,2")
True
>>> validate("-123,2")
True
# Invalid inputs
>>> validate("123,")
False
>>> validate("123,2,")
False
>>> validate("+123,2,")
False
>>> validate("hello")
False
Edit
I understand now that you want to check in real-time if the input is valid.
So here is an example of what you can do:
import tkinter as tk
import re
def validate(string):
regex = re.compile(r"(\+|\-)?[0-9,]*$")
result = regex.match(string)
return (string == ""
or (string.count('+') <= 1
and string.count('-') <= 1
and string.count(',') <= 1
and result is not None
and result.group(0) != ""))
def on_validate(P):
return validate(P)
root = tk.Tk()
entry = tk.Entry(root, validate="key")
vcmd = (entry.register(on_validate), '%P')
entry.config(validatecommand=vcmd)
entry.pack()
root.mainloop()
The validate function now checks more or less the same thing, but more loosely.
Then if the regex results in a match, some additional checks are performed:
Allow the input to be empty;
Prevent the input to have more than one '+'/'-', and more than one ',';
Ignore a match against the empty string, because the pattern allows it, so "a" would result in a match but we don't want it.
The command is registered, and works with the '%P' parameter, that corresponds to the string if the input were accepted.
Please not however that forcing the input to be always correct is somewhat harsh, and might be counter-intuitive.
A more commonly used approach is to update a string next to the entry, and have the user know when their input is valid or invalid.
I have numerous regular expressions, such as:
quantity_validation = re.compile("""^-?[0-9]+$""", re.I)
I use these regex in functions like:
def validate_quantity(self, value):
if value != self.context.quantity:
# while not self.quantity_validation.match(value):
# return 0, "Value is not a number"
if not self.quantity_validation.match(value):
return 0, "Value is not a number"
return 1, ""
and the validation method:
#view_config(name="validate", renderer='json')
def validate(self, full={}):
def do_validation(field, value):
message = ""
valid = 1
if getattr(self, 'validate_%s' % field, False):
valid, message = getattr(self, 'validate_%s' % field)(value)
out = dict(message=message, valid=valid, value=value)
return out
if not full:
field = self.request.params.get('field')
if not field:
return "INVALID"
return do_validation(field, self.request.params.get('value'))
else:
return dict(((field, do_validation(field, value)) for field, value in full.items()))
On the HTML form I have a modal with a quantity field on which I do ajax validation(I use jqBootstrapValidation to validate my forms)
The regex validation works fine until I type a + then the validation stop working until I refresh the page.
Why does the validation stop working?
Is it perhaps a bug in jqBootstrapValidation?
or is it a bug in the re module?
You are not encoding your URLs/inputs correctly. The '+' gets parsed into a ' ' when you pass it via the parameter string. Try '%2B' instead which should be decoded into a '+' through the form library.
def clean(self):
cleaned_data = self.cleaned_data
current_pass = cleaned_data['current_pass']
new_pass = cleaned_data['new_pass']
new_pass2 = cleaned_data['new_pass2']
if current_pass or new_pass or new_pass2:
if not current_pass:
raise forms.ValidationError("- You must enter your current password.")
if not new_pass:
raise forms.ValidationError("- You must enter a new password.")
if not new_pass2:
raise forms.ValidationError("- You must re-confirm your new password.")
return cleaned_data
Right now, I raise my errors. But this means that the other errors won't pop up. it ends the function when I raise the first one. What if I want to have all 3 errors?
A solution could be to bind those errors to the relevant fields, as explained in the docs.
Your code would look like this:
def clean(self):
cleaned_data = self.cleaned_data
current_pass = cleaned_data['current_pass']
new_pass = cleaned_data['new_pass']
new_pass2 = cleaned_data['new_pass2']
if current_pass or new_pass or new_pass2:
if not current_pass:
self._errors["current_pass"] = self.error_class(["- You must enter your current password."])
if not new_pass:
self._errors["new_pass"] = self.error_class(["- You must enter a new password."])
if not new_pass2:
self._errors["new_pass2"] = self.error_class(["- You must re-confirm your new password."])
del cleaned_data["current_pass"]
del cleaned_data["new_pass"]
del cleaned_data["new_pass2"]
return cleaned_data
Please beware that I could not test it personally though.
By using the clean method, you are doing per-form validation. The validator for the whole form has failed.
For individual fields, you should be using the clean_fieldname methods instead of clean which runs after individual field validation.
If you use the clean_fieldname, you can access the errors in forminstance.errors or forminstance.field.errors
def clean_current_pass(self):
data = self.cleaned_data.get('current_pass'):
if not data:
raise forms.ValidationError('- You must enter your current password.')
return data
def clean_new_pass(self):
data = self.cleaned_data.get('new_pass'):
if not data:
raise forms.ValidationError("- You must enter a new password.")
return data
def clean_new_pass2(self):
data = self.cleaned_data.get('new_pass2'):
if not data:
raise forms.ValidationError('- You must re-confirm your new password.')
return data
{{ myform.errors }} would give you all errors in your template.
(This is a Python question not a Django question.)
This is as it should be: when an error is raised it should immediately propagate upwards until it's handled. You can't expect the rest of the function to be evaluated, because the error hasn't been dealt with yet!
Probably the easiest and cleanest method is to rewrite the code:
check = (current_pass, new_pass, new_pass2)
code = ("You must enter your current password.", ...)
err = ''.join(code for i, code in enumerate(codes) if check[i])
if err:
raise forms.ValidationError(err)