pyinputplus allowRegexes keyword allows any input

Viewed 723

I am trying to use pyinputplus to validate some inputs for storing flight arrangements. In particular I am having issues with the allowRegexes keyword.

Here is my code (python 3.8.5):

>>> import pyinputplus as pyip
>>> confirmation_number = pyip.inputStr(
...             prompt='Confirmation number = ',
...             allowRegexes=[r'\w{6}']
...              ).upper()
Confirmation number = #$%}|+_
>>> name = pyip.inputStr(
...             prompt='Reservation name = ',
...             allowRegexes=[r'([A-Za-z]+\s[A-Za-z]+)']
...             ).title()
Reservation name = F1rstn@me La$tnam3
>>> confirmation_number
'#$%}|+_'
>>> name
'F1Rstn@Me La$Tnam3'

I would have expected that both of these inputs would have been blocked. Apparently, both will allow any input.

The docs only explain that allowRegexes will override blockRegexes, and any examples online I've found provide examples of regex and phone numbers, but not what is/isn't blocked accordingly.

Possible duplicate of Why is the allowRegexes keyword in pyinputplus allowing ABC in pyip.inputNum(allowRegexes=[r'(C)+'])?

1 Answers

The problem is that your understanding of the allowRegexes parameter is not entirely accurate. Although it is partially related, your question is not a duplicate of Why is the allowRegexes keyword in pyinputplus allowing ABC in pyip.inputNum(allowRegexes=[r'(C)+'])?. In fact, there are two different misconceptions in your two function calls.

  1. You seem to expect that only inputs that match the regex in allowRegexes will pass validation. But in fact, the behavior is that inputs for which the regex in allowRegexes matches will be valid in addition to those, that are already found valid by the respective function that is used.

    In your case, that is the function pyinputplus.inputStr(), which accepts any string as input. So your first function call says "Accept any input and additionally any input that contains a sequence of any six consecutive word characters'. Clearly, the second part is redundant, so this is why you observe the described behavior.

    To remedy this, you have several options:

    (a) Use a combination of blockRegexes and allowRegexes:

    As you correctly wrote, allowRegexes will override blockRegexes. So you can use that to your advantage by first blocking any input using the regex .* and then allowing only the input that you want:

    >>> confirmation_number = pyip.inputStr(
    ...             prompt='Confirmation number = ',
    ...             blockRegexes=[r'.*'],
    ...             allowRegexes=[r'\w{6}']
    ...            ).upper()
    Confirmation number = #$%}|+_
    This response is invalid.
    

    (b) Use pyinputplus.inputRegex() instead

    There is a function that directly does what you expected. It is pyinputplus.inputRegex():

    Prompt the user to enter a string that matches the provided regex string (or regex object) and flags. Returns the entered string

    >>> confirmation_number = pyip.inputRegex(r'\w{6}',
    ...             prompt='Confirmation number = '
    ...            ).upper()
    Confirmation number = #$%}|+_
    '#$%}|+_' does not match the specified pattern.
    Confirmation number = 123.45
    '123.45' does not match the specified pattern.
    Confirmation number = abcde
    'abcde' does not match the specified pattern.
    Confirmation number = abCD_E
    >>> 
    

    This takes care of your first problem.

  2. If you attempt the same solution outlined above for your second example, the string will still be accepted. The reason for that is that the behavior of allowRegexes is actually different than you expected in a second way: The regex does not need to match the entire input. Instead, if the regex set in allowRegexes matches any part of the input, the whole string will be valid (respectively invalid, if used in blockRegexes).

    Let me explain using your example F1rstn@me La$tnam3: The regex that you used is ([A-Za-z]+\s[A-Za-z]+). While this does not match the complete string F1rstn@me La$tnam3, it partially matches the middle part me La. Therefore, the whole input is accepted as being valid. This also leads to the first example above accepting any and all strings that have a 6 word character sequence somewhere in them.

    To remedy this, tune your regexes so that they accept only exactly the sequence you want to allow. In your case, this is easily achieved by wrapping the regexes in ^ (asserts position at start of a line) and $ (asserts position at end of a line):

    >>> confirmation_number = pyip.inputRegex(r'^\w{6}$',
    ...              prompt='Confirmation number = ',
    ...     ).upper()
    Confirmation number = #$%}|+_
    '#$%}|+_' does not match the specified pattern.
    Confirmation number = 123.45
    '123.45' does not match the specified pattern.
    Confirmation number = abcde
    'abcde' does not match the specified pattern.
    Confirmation number = 1234567890
    '1234567890' does not match the specified pattern.
    Confirmation number = abcdefghij
    'abcdefghij' does not match the specified pattern.
    Confirmation number = a2cD_e
    >>> 
    

    See https://regex101.com/r/I5OU0L/1

     >>> name = pyip.inputRegex(r'^([A-Za-z]+\s[A-Za-z]+)$',
     ...     prompt='Reservation name = '
     ...     ).title()
     Reservation name = F1Rstn@Me La$Tnam3
     'F1Rstn@Me La$Tnam3' does not match the specified pattern.
     Reservation name = F1Rstn@Me Lastname
     'F1Rstn@Me Lastname' does not match the specified pattern.
     Reservation name = Firstnaem La$Tnam3
     'Firstnaem La$Tnam3' does not match the specified pattern.
     Reservation name = Firstname Lastname
     >>> 
    

    See https://regex101.com/r/hDh40k/1

    This second issue is actually the same as the one in the issue you linked in your question as possible duplicate...

Related