Validations and Constraints... Worth the Effort
Tags: tutorial, flask, python, beginners
Published: 2023-04-21 19:07:28

Are you struggling with how to ensure that the data you're working with is valid? Don't worry, you're not alone. As a developer, validating data is a critical aspect of ensuring that an application is secure, stable, and predictable. In other words, validation is a way to make sure that the data we receive is the data we expect. 

Validations can be a tricky concept to wrap your head around, but they're an important part of coding. In this post, we'll explore how to use validations to ensure that your data is accurate and error-free.

To start, let's take a look at the code snippet below: 

```
class Mission(db.Model, SerializerMixin):
    __tablename__ = 'missions'
    __table_args__ = (
        db.CheckConstraint('length(content) >= 250', name='max_content_length'),
    )

    serialize_rules = ('-scientist.missions', '-planet.missions', '-created_at', '-updated_at',)


    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)
    year = db.Column(db.Integer)
    parameter = db.Column(db.String)
    summary = db.Column(db.String, db.CheckConstraint('len(summary) <= 250', name='min_summary_length'))
    content = db.Column(db.String, db.CheckConstraint('len(content) > 250', name='max_content_length2'))

    scientist_id = db.Column(db.Integer, db.ForeignKey('scientists.id'))
    planet_id = db.Column(db.Integer, db.ForeignKey('planets.id'))
```

This code uses an example of a Mission, Planet, and Scientist. Our Mission class has various attributes, including a name, year, parameter, summary, content, as well as relationships to both a Scientist and Planet Class. To validate these attributes it has to include several validation methods.

Each validation method in this code will use the **@validates** decorator to specify which attribute it is validating. This decorator allows you to specify a function that is called whenever the attribute is set or updated, allowing you to ensure that the data is valid at all times.

For instance, to validate the name attribute, we use a validate_name method:

```
@validates('name')
def validate_name(self, key, name):
    if not name:
        raise ValueError("Mission must have a name")
    return name
```

In the code above, we check if the name attribute exists and if it doesn't, we raise a **ValueError**. Otherwise, we return the name attribute.

We also use the **@validates** decorator to validate the year, parameter, content, summary, scientist_id, and planet_id attributes. In each case, we use conditional statements and raise a **ValueError** if the validation fails.

For example, to validate the year attribute, we can use a validate_year method:

```
@validates('year')
def validate_year(self, key, year):
    if len(str(year)) != 4:
        raise ValueError("Mission Year must be 4 digits.")
    return year
```

In the code above, we check if the year attribute is a 4-digit number. If it's not, we raise a **ValueError**.

There are many methods in validation, and it will sometimes require some tricky and logical thinking to create ones that fit your exact circumstances. Validations ensure that the data we store is clean and consistent, which helps to prevent errors and unexpected behavior in our application. However, validations alone are not enough to guarantee data integrity. We also need to use constraints to ensure that our data meets specific requirements. Enter constraints.

If you scroll back up and look at the initial Mission code, you will see:

```
name = db.Column(db.String, nullable=False)
```

That **nullable=False** is a constraint. And there are many different ones, including **unique=True**, **default=Value**, and **max_length=Value**. However these may not fit all circumstances. So there is another kind of restraint I will introduce you too. **CheckConstraint**

When a **CheckConstraint** is defined within a column definition, it is tied to that column only. On the other hand, when a **CheckConstraint** is defined in the **__table_args__** of a table, it is applied to the entire table. We can use the **db.CheckConstraint** both specifically and table-wide to specify the minimum and maximum length for the content and summary attributes. This is how that might look:

```
__table_args__ = (
    db.CheckConstraint('length(content) >= 250', name='max_content_length'),
)

summary = db.Column(db.String, db.CheckConstraint('len(summary) <= 250', name='min_summary_length'))
```

Both of the above **CheckConstraint**s are valid and can be used as circumstances dictate. Important note: You must give a CheckConstraint a unique name, or it will not function properly. It must also all be contained with (' '), which makes the constraint a string. Don't worry, it will read it as if it was not forced to be a string. 

So, why are validations and constraints so important? Well, in short, they help ensure that your code works correctly. Without validations, it's easy to accidentally introduce errors into your data, which can cause problems down the line. And constraints provide an additional layer of data protection by preventing data that doesn't meet specific criteria from being stored in the database. By validating your data, you can catch errors early and ensure that your code is working as intended.

In summary, validation and constraints are essential tools in ensuring that our application data is secure and consistent. And they help prevent errors and unexpected behavior in our application, which ultimately benefits the end-users, and is essential for any successful project. They are a critical part of coding. Of course, like any coding concept, validations and constraints can be challenging to master. But with persistence and determination, you can learn anything. When you're first starting out, it's natural to feel overwhelmed or unsure of yourself. By reading and experimenting, you can start to get a better understanding of how validations work and how to use them effectively. So, keep pushing yourself to learn more. Remember, learning to code may be difficult, but everyone has to start from somewhere. By being diligent and purposeful, and never giving up, you can overcome any obstacle that comes your way. With time and practice, you can master even the most complex coding concepts.