Pydantic: Validate User Email and Password (2 Ways)

Updated: December 1, 2023 By: Khue Post a comment

Validating user email and password is one of the most common tasks in software development nowadays since most applications require users to create accounts and log in. This practical, example-based article will walk you through a couple of different ways to validate user email and password by using Pydantic (We’ll use the latest version of Pydantic in the upcoming examples. If you are totally new to the library, please see this article first).

Using EmailStr and constr types

This approach uses the built-in types EmailStr and constr from Pydantic to validate the user email and password. EmailStr is a type that checks if the input is a valid email address. constr is a type that allows specifying constraints on the length and format of a string.

email-validator is an optional dependency that is needed for the EmailStr type so you have to install it before importing EmailStr into your code:

pip install email-validator

From here, the steps are:

  1. Import EmailStr and constr from Pydantic.
  2. Define a User model that has email and password fields of type EmailStr and constr respectively.
  3. Specify the constraints on the password field using the min_length and max_length parameters of constr. In addition, you can use the pattern parameter to declare a regex pattern that the password must match.
  4. Create an instance of the User model with some input data and check if it is valid or raises a ValidationError.

Example:

from pydantic import BaseModel, EmailStr, constr, ValidationError

# Defome User model
class User(BaseModel):
    email: EmailStr
    password: constr(
        min_length=8,
        max_length=20
    )

Test it with valid data:

try:
    user = User(email="[email protected]", password="Passw0rd!")
    print(user)
    # Success
    # email='[email protected]' password='Passw0rd!'
except ValidationError as e:
    print(e)

And give it a try with invalid data to see what happens:

try:
    user = User(email="test", password="123")
    print(user)
except ValidationError as e:
    print(e)
    # 2 validation errors for User
    # email
    #   value is not a valid email address: The email address is not valid. It must have exactly one @-sign. [type=value_error, input_value='test', input_type=str]
    # password
    #   String should have at least 8 characters [type=string_too_short, input_value='123', input_type=str]

This approach is simple and neat but it may not be flexible enough to customize the validation logic or error messages for non-common scenarios or requirements.

Using custom validators

The main idea here is to use custom validators from Pydantic to validate user email and password. Custom validators are functions that take the input value and return a modified value or raise a ValueError if the validation fails. They can be decorated with @field_validator or @model_validator to apply them to specific fields or the whole model, respectively.

The steps are:

  1. Import BaseModel and field_validator from Pydantic.
  2. Define a User model that has email and password fields of type str.
  3. Write a custom validator function for the email field that checks if the input is a valid email address using regular expressions (you can use a third-party library if you want).
  4. Write a custom validator function for the password field that checks if the input meets some criteria on the length and format of the password.
  5. Decorate the validator functions with @field_validator and specify the field names they apply to.

The example below will give you more clarity. It requires a password to have at least 8 characters, at least one uppercase letter, at least one lowercase letter, and at least one digit.

from pydantic import BaseModel, field_validator
import re

# Define a Pydantic model for a user
class User(BaseModel):
    email: str
    password: str

    # Define a validator for the email field
    @field_validator("email")
    def check_email(cls, value):
        # use a regex to check that the email has a valid format
        email_regex = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
        if not re.match(email_regex, value):
            raise ValueError("Invalid email address")
        return value

    # Define a validator for the password field
    @field_validator("password")
    def check_password(cls, value):
        # convert the password to a string if it is not already
        value = str(value)
        # check that the password has at least 8 characters, one uppercase letter, one lowercase letter, and one digit
        if len(value) < 8:
            raise ValueError("Password must have at least 8 characters")
        if not any(c.isupper() for c in value):
            raise ValueError("Password must have at least one uppercase letter")
        if not any(c.islower() for c in value):
            raise ValueError("Password must have at least one lowercase letter")
        if not any(c.isdigit() for c in value):
            raise ValueError("Password must have at least one digit")
        return value

Let’s test our model:

# Create a user with a valid email and password
try:
    u1 = User(email="[email protected]", password="Password1")
    print(u1)
    # Output: email='[email protected]', password='Password1'
except ValueError as e:
    print(e)

# Create a user with an invalid email
try:
    u2 = User(email="test", password="Password1")
    print(u2)
except ValueError as e:
    print(e)
    # 1 validation error for User
    # email
    # Value error, Invalid email address [type=value_error, input_value='test', input_type=str]

# Create a user with an invalid password
try:
    u3 = User(email="[email protected]", password="password")
    print(u3)
except ValueError as e:
    print(e)
    # 1 validation error for User
    # password
    #  Value error, Password must have at least one uppercase letter [type=value_error, input_value='password', input_type=str]

This approach is more verbose than the first one, but it can handle complex or cross-field validation scenarios.

Conclusion

If you want to become a professional web developer with Python, tasks related to validating user email and password cannot be avoided. Therefore, mastering how to complete those tasks with Pydantic is extremely important and will save you a lot of time in the future. Take a close look at the examples, try changing a few lines of code, and see what happens next. Happy coding & have a nice day with Pydantic and Python programming!