Categories
Python

Validating Python Inputs with PyInputPlus

Python is a convenient language that’s often used for scripting, data science, and web development.

In this article, we’ll look at how to validating Python inputs with the PyInputPlus.

Validation Inputs

We use the PyInputPlus package to validate inputs from retrieved from the command line.

To use it, we have to install it. We run pip install pyinputplus to install the package.

Then we can use the following functions in the module to validate inputs:

  • inputStr — it’s like input but we can validate custom validations into it.
  • inputNum — ensures user enters a number and returns an int or float depending on if the number has a decimal point or not
  • inputChoice — ensures user enters of one the provided choices
  • inputMenu — it’s like inputChoice but provides a menu with number or lettered options
  • inputDatetime — ensures user enter a date and time
  • inputYesNo — ensures user enters yes or no
  • inputBool — ensures user enters True or False
  • inputEmail — ensures user enters email address
  • inputFilePath — checks that a user enters a file path
  • inputPassword — like input , but displays * in place of whatever is entered

We can use the module as follows:

import pyinputplus  
print('What is your age?')  
age = pyinputplus.inputNum()  
print('Your age is', age)

The code above asks user to enter their age. If they enter their age, then we display the last line.

Otherwise, we display an error message until they enter a number.

We can pass in the prompt text to the function. For instance, we can write:

import pyinputplus  
age = pyinputplus.inputNum(prompt='What is your age?')  
print('Your age is', age)

It works the same way as before except the 'What is your age?’ message doesn’t add a new line.

The min, max, greaterThan, and lessThan Keyword Arguments

We can check if the number entered is in the range we want with min , max , greaterThan , and lessThan keywords.

They do as their names suggest. For instance:

import pyinputplus  
age = pyinputplus.inputNum(prompt='What is your age?',  min=0)  
print('Your age is', age)

The code above will check if a number 0 or bigger is entered. If we enter an invalid number, we’ll get an error message until we enter a valid number.

The blank Keyword Argument

We can disallow blank inputs with by passing in a boolean argument for the blank parameter.

For example, we can write:

import pyinputplus  
age = pyinputplus.inputNum(prompt='What is your age?',  min=0, blank=False)  
print('Your age is', age)

Then we get Blank values are not allowed. error if we entered a blank value. We can’t proceed until we enter a valid number.

The limit, timeout, and default Keyword Arguments

PyInputPlus functions will continue to ask the same question until we enter a valid value.

To change this, we can use pass in an argument for the limit parameter to limit the number of tries allowed.

For instance, we can write:

import pyinputplus  
age = pyinputplus.inputNum(prompt='What is your age?',  min=0, limit=2)  
print('Your age is', age)

to limit the number of tries for answer the 'What is your age?' question to 2.

When we don’t enter a valid number after 2 tries, we get an error.

To set a default value, we can pass in an argument to the default parameter. For instance, we can write:

import pyinputplus  
age = pyinputplus.inputNum(prompt='What is your age?',  min=0, default='0', limit=2)  
print('Your age is', age)

When running the code above, if we didn’t enter a valid number after 2 tries, then it’ll print 'Your age is 0' on the screen since we set the default value to 0.

We can set the timeout parameter to limit the time that our program waits for an input to be entered.

For example, we can write:

import pyinputplus  
age = pyinputplus.inputNum(prompt='What is your age?',  timeout=1)  
print('Your age is', age)

After waiting for a second, we’ll get a TimeoutException thrown if nothing is entered.

The allowRegexes and blockRegexes Keyword Arguments

We can set a list of regex strings to the allowRegexes parameter to the PyInputPlus functions.

For example, if we want to make sure the user enters a phone number, we can write:

import pyinputplus  
phone = pyinputplus.inputNum(prompt='What is your phone?',  allowRegexes=[r'\d{3}-\d{3}-\d{4}', r'None'])  
print('Your phone is', phone)

In the code above, we allow phone numbers to be entered as the input value by setting a list of regex with the phone number regex to it.

Then we have to enter a phone number or 'None' before we can proceed.

Conclusion

We can use the PyInputPlus package to validate command line input values.

It has functions to checking various kinds of inputs like numbers, emails, date and time, yes or no, and so on.

Also, we can limit the range of the values that are entered for numbers and so check against regexes so that anything can be checked for the given format.

Categories
Python

Custom Input Validation Python Inputs with PyInputPlus

ython is a convenient language that’s often used for scripting, data science, and web development.

In this article, we’ll look at how to do custom input validation with PyInputPlus.

Passing a Custom Validation Function to inputCustom()

We can use the inputCustom function to validate any input by users,

It accepts a single string argument of what the user entered, raises an exception if the string fails validation, returns None if inputCustom should return the string unchanged, and returns non-None values if inputCustom should return a different string from the one that’s entered.

The function is passed in as the first argument to inputCustom .

For instance, we can use it as follows:

import pyinputplus  
import re  
def check_phone(phone):    
  if bool(re.match(r'\d{3}-\d{3}-\d{4}', phone)) == False:  
    raise Exception('Invalid phone number')  
  return phoneprint('What is your phone number?')  
phone_num = pyinputplus.inputCustom(check_phone)  
print('Your number is', phone_num)

In the code above, we defined the check_phone function, which checks if the entered phone number is a valid phone number or not. If it isn’t, then it raises an exception.

Otherwise, we return the phone number.

Then we pass in the check_phone function to the inputCustom function.

When we run the program, then we can’t get past the prompt until we entered a valid phone number.

Otherwise, we get an error.

Whatever we return in check_phone is returned by inputCustom if validation succeeds.

Creating a Menu with inputMenu

We can use the inputMenu function to create a simple menu. It takes a list of options as strings. Also, we can set the lettered option to add letters to our choices if set to True . We can add numbers to our menu by setting the numbered option to False .

For instance, we can write the following to create a simple menu:

import pyinputplusfruit = pyinputplus.inputMenu(['apple', 'banana', 'orange'], lettered=True, numbered=False)  
print('Chosen fruit', fruit)

Then we can type in a letter to make a choice.

Narrowing Choices with inputChoice

We can use the inputChoice function to allow users to enter a value. The only valid inputs are the choices that we set.

For instance, we can write:

import pyinputplusfruit = pyinputplus.inputChoice(\['apple', 'banana', 'orange'\])  
print('Chosen fruit', fruit)

Then we get:

Please select one of: apple, banana, orange

displayed on the screen.

Once we entered one of the answers listed above, we can proceed.

Conclusion

We can use the inputCustom function to validate any input we want. It takes a validation function as an argument.

The function we pass in takes the input value as a parameter.

It should raise an exception if the input fails validation. Otherwise, it should return the value that’s entered or something derived from it.

inputCustom returns whatever it’s returned with the validation function.

We can create a menu with the inputMenu function and create an input that only accepts a few choices with the inputChoice function.

Categories
Python

Manipulating File Paths with Python

Python is a convenient language that’s often used for scripting, data science, and web development.

In this article, we’ll look at how to read and write files with Python.

Files and File Paths

A file has a filename to reference the file. It also has a path to locate the file’s location.

The path consists of the folder, they can be nested and they form the path.

Backslash on Windows and Forward Slash on macOS and Linux

In Windows, the path consists of backslashes. In many other operating systems like macOS and Linux, the path consists of forward slashes.

Python’s standard pathlib library knows the difference and can sort them out accordingly. Therefore, we should use it to construct paths so that our program will run everywhere.

For instance, we can import pathlib as follows and create a Path object as follows:

from pathlib import Path  
path = Path('foo', 'bar', 'foo.txt')

After running the code, path should be a Path object like the following if we’re running the program above on Linux or macOS:

PosixPath('foo/bar/foo.txt')

If we’re running the code above on Windows, we’ll get a WindowsPath object instead of a PosixPath object.

Using the / Operator to Join Paths

We can use the / operator to join paths. For instance, we can rewrite the path we had into the following code:

from pathlib import Path  
path = Path('foo')/'bar'/'foo.txt'

Then we get the same result as before.

This will also work on Windows, macOS, and Linux since Python will sort out the path accordingly.

What we shouldn’t use is the string’s join method because the path separator is different between Windows and other operating systems.

For instance:

path = '/'.join(['foo', 'bar', 'foo.txt'])

isn’t going to work on Windows since the path has forward slash.

The Current Working Directory

We can get the current working directory (CWD), which is the directory the program is running on.

We can change the CWD with the os.chdir function and get the current CWD with the Path.cwd function.

For instance, we can write:

from pathlib import Path  
import os  
print(Path.cwd())  
os.chdir(Path('foo')/'bar')  
print(Path.cwd())

Then we get:

/home/runner/AgonizingBasicSpecialist  
/home/runner/AgonizingBasicSpecialist/foo/bar

as the output.

As we can see, chdir changed the current working directory, so that we can use manipulate files in directories other than the ones that the program is running in.

The Home Directory

The home directory is the root directory of the profile folder of the user’s user account.

For instance, we can write the following:

from pathlib import Path  
path = Path.home()

Then the value of path is something likePosixPath(‘/home/runner’) .

Absolute vs. Relative Paths

An absolute path is a path that always begins with the root folder. A relative is a path that’s relative to the program’s current working directory.

For example, on Windows, C:\Windows is an absolute path. A relative path is something like .\foo\bar . It starts with a dot and foo is inside the current working directory.

Creating New Folders Using the os.makedirs() Function

We can make a new folder with the os.makedirs function.

For instance, we can write:

from pathlib import Path  
Path(Path.cwd()/'foo').mkdir()

Then we make a foo directory inside our current working directory.

Handling Absolute and Relative Paths

We can check if a path is an absolute path with the is_absolute method.

For instance, we can write:

from pathlib import Path  
is_absolute = Path.cwd().is_absolute()

Then we should see is_absolute being True since Path.cwd() returns an absolute path.

We can call os.path.abspath to returns a string with of the absolute path of the path argument that we pass in.

For instance, given that we have the directory foo in the current working directory, we can write:

from pathlib import Path  
import os  
path = os.path.abspath(Path('./foo'))

to get the absolute path of the foo folder.

We then should get something like:

'/home/runner/AgonizingBasicSpecialist/foo'

as the value of path .

os.path.isabs(path) is a method that returns True is a path that is absolute.

The os.path.relpath(path, start) method will return a string of the relative path from the start path to path .

If start isn’t provided, then the current working directory is used as the start path.

For instance, if we have the folder /foo/bar in our home directory, then we can get the path of ./foo/bar relative to the home directory by writing:

from pathlib import Path  
import os  
path = os.path.relpath(Path.home(), Path('./foo')/'bar')

Then the path has the value ‘../../..’ .

Conclusion

We can use the path and os modules to construct and manipulate paths.

Also, we can also use the / with Path objects to create a path that works with all operating systems.

We can also path in paths to the Path function to construct paths.

Python also has methods to check for relative and absolute paths and the os module can construct relative paths from 2 absolute paths.

Categories
Python

Using Regex with Python

Python is a convenient language that’s often used for scripting, data science, and web development.

In this article, we’ll look at how to use regex with Python to make finding text easier.

Finding Patterns of Text with Regular Expressions

Regular expressions, or regexes, are descriptions for a pattern of text.

For instance, \d represents a single digit. We can combine characters to create regexes to search text.

To use regexes to search for text, we have to import the re module and then create a regex object with a regex string as follows:

import re  
phone_regex = re.compile('\d{3}-\d{3}-\d{4}')

The code above has the regex to search for a North American phone number.

Then if we have the following string:

msg = 'Joe\'s phone number is 555-555-1212'

We can look for the phone number inside msg with the regex object’s search method as follows:

import re  
phone_regex = re.compile('\d{3}-\d{3}-\d{4}')  
msg = 'Joe\'s phone number is 555-555-1212'  
match = phone_regex.search(msg)

When we inspect the match object, we see something like:

<re.Match object; span=(22, 34), match='555-555-1212'>

Then we can return a string representation of the match by calling the group method:

phone = match.group()

phone has the value '555-555-1212' .

Grouping with Parentheses

We can use parentheses to group different parts of the result into its own match entry.

To do that with our phone number regex, we can write:

phone_regex = re.compile('(\d{3})-(\d{3})-(\d{4})')

Then when we call search , we can either get the whole search string, or individual match groups.

group takes an integer that lets us get the parts that are matched by the groups.

Therefore, we can rewrite our program to get the whole match and the individual parts of the phone number as follows:

import re  
phone_regex = re.compile('(\d{3})-(\d{3})-(\d{4})')  
msg = 'Joe\'s phone number is 123-456-7890'  
match = phone_regex.search(msg)  
phone = match.group()  
area_code = match.group(1)  
exchange_code = match.group(2)  
station_code = match.group(3)

In the code above, phone should be ‘123–456–7890’ since we passed in nothing to group. Passing in 0 also returns the same thing.

area_code should be '123' since we passed in 1 to group , which returns the first group match.

exchange_code should be '456' since we passed in 2 to group , which returns the 2nd group match.

Finally, station_code should be '7890' since we passed in 3 to group , which returns the 3rd group match.

If we want to pass in parentheses or any other special character as a character of the pattern rather than a symbol for the regex, then we have to put a \ before it.

Matching Multiple Groups with the Pipe

We can use the | symbol, which is called a pipe to match one of many expressions.

For instance, we write the following to get the match:

import re  
name_regex = re.compile('Jane|Joe')  
msg = 'Jane and Joe'  
match = name_regex.search(msg)  
match = match.group()

match should be 'Jane' since this is the first match that’s found according to the regex.

We can combine pipes and parentheses to find a part of a string. For example, we can write the following code:

import re  
snow_regex = re.compile(r'snow(man|mobile|shoe)')  
msg = 'I am walking on a snowshoe'  
snow_match = snow_regex.search(msg)  
match = snow_match.group()  
group_match = snow_match.group(1)

to get the whole match with match , which has the value 'snowshoe' .

group_match should have the partial group match, which is 'shoe' .

Optional Matching with the Question Mark

We can add a question mark to the end of a group, which makes the group optional for matching purposes.

For example, we can write:

import re  
snow_regex = re.compile(r'snow(shoe)?')  
msg = 'I am walking on a snowshoe'  
msg_2 = 'I am walking on snow'  
snow_match = snow_regex.search(msg)  
snow_match_2 = snow_regex.search(msg_2)

Then snow_match.group() returns 'snowshoe' and snow_match.group(1) returns 'shoe' .

Since the (shoe) group is optional, snow_match_2.group() returns 'snow' and snow_match_2.group(1) returns None .

Conclusion

We can use regexes to find patterns in strings. They’re denoted by a set of characters that defines a pattern.

In Python, we can use the re module to create a regex object from a string.

Then we can use it to do searches with the search method.

We can define groups with parentheses. Once we did that, we can call group on the match object returned by search .

The group is returned when we pass in an integer to get it by their position.

We can make groups optional with a question mark appended after the group.

Categories
Python

More Things We Can Do With Regexes and Python

Python is a convenient language that’s often used for scripting, data science, and web development.

In this article, we’ll look at newline matches, case insensitive matching, and the sub method.

Matching Newlines with the Dot Character

We can use the re.DOTALL constant to match newlines.

For instance, we can use it as in the following code:

import re  
regex = re.compile(r'.\*', re.DOTALL)  
matches = regex.search('Jane\\nJoe')

Then we get ‘Jane\nJoe’ as the value returned bymatches.group() .

Without re.DOTALL , as in the following example:

import re  
regex = re.compile(r'.\*')  
matches = regex.search('Jane\\nJoe')

we get ‘Jane’ as the value returned bymatches.group() .

Summary of Regex Symbols

The following is a summary of regex symbols:

  • ? — matches 0 or 1 of the preceding group
  • * — matches 0 or more of the preceding group
  • + — matches one or more of the preceding group
  • {n} — matches exactly n of the preceding group
  • {n,} — matches n or more of the preceding group
  • {,n} — matches 0 to n of the preceding group
  • {n,m} — matches n to m of the preceding group
  • {n,m}? or *? or +? performs a non-greedy match of the preceding group
  • ^foo — matches a string beginning with foo
  • foo$ — matches a string that ends with foo
  • . matches any character except for new kine
  • \d , \w , and \s matches a digit, word, or space character respectively
  • \D , \W , and \S match anything except a digit, word, or space character respectively
  • [abc] — matches any character between the brackets like a, , b , or c
  • [^abc] — matches any character but a , b or c

Case-Insensitive Matching

We can pass in re.I to do case insensitive matching.

For instance, we can write:

import re  
regex = re.compile(r'foo', re.I)  
matches = regex.findall('FOO foo fOo fOO Foo')

Then matches has the value [‘FOO’, ‘foo’, ‘fOo’, ‘fOO’, ‘Foo’] .

Substituting Strings with the sub() Method

We can use the sub method to replace all substring matches with the given string.

For instance, we can write:

import re  
regex = re.compile(r'\\d{3}-\\d{3}-\\d{4}')  
new\_string = regex.sub('SECRET', 'Jane\\'s number is 123-456-7890. Joe\\'s number is 555-555-1212')

Since sub replaces the substring matches passed in as the 2nd argument and a new string is returned, new_string has the value of:

"Jane's number is SECRET. Joe's number is SECRET"

Verbose Mode

We can use re.VERBOSE to ignore whitespaces and comments in a regex.

For instance, we can write:

import re  
regex = re.compile(r'\\d{3}-\\d{3}-\\d{4} # phone regex', re.VERBOSE)  
matches = regex.findall('Jane\\'s number is 123-456-7890. Joe\\'s number is 555-555-1212')

Then matches has the value [‘123–456–7890’, ‘555–555–1212’] since the whitespace and comment in our regex is ignored by passing in the re.VERBOSE option.

Combining re.IGNORECASE, re.DOTALL, and re.VERBOSE

We can combine re.IGNORECASE , re.DOTALL , and re.VERBOSE with a pipe (|) operator.

For instance, we can do a case-insensitive and ignore whitespace and comments by writing:

import re  
regex = re.compile(r'jane # jane',  re.IGNORECASE | re.VERBOSE)  
matches = regex.findall('Jane\\'s number is 123-456-7890. Joe\\'s number is 555-555-1212')

The matches has the values ['Jane'] since we passed in re.IGNORECASE and combined it with re.VERBOSE with the | symbol to do a case-insensitive search.

Conclusion

We can pass in different arguments to the re.compile method to adjust how regex searches are done.

re.IGNORECASE lets us do a case-insensitive search.

re.VERBOSE makes the Python interpreter ignore whitespace and comments in our regex.

re.DOTALL let us search for matches with newline characters.

The 3 constants above can be combined with the | operator.

The sub method makes a copy of the string, then replace all the matches with what we passed in, then returns the string with the replacements.