Serializing America’s Next Top Model (Class)

varlotte

Charlotte Bush

Posted on October 15, 2023

Serializing America’s Next Top Model (Class)

Delving into backend dev has been fascinating. I love thinking about how data is organized and displayed, and how it “talks” to other data and servers, so this was a fun time for me. As I began to learn more, though, I realized I had a ton of questions about serialization, so I wanted to create an explainer based on some of my own questions.

Tyra Banks holding photos, text says: "I have two model classes in my hands ("-loser_contestant",)"

What is serialization?

Serialization is just the process of converting a data object into a format suitable for transmission and storage. From a practical standpoint, this means it takes in an object and turns it into a giant string that you can send over the internet or store in a database or file. So far, so good. In Python, we serialize with functions like jsonify() whereas in JavaScript we use JSON.serialize.

So wait, if there’s a serialization, does this mean there’s a deserialization?

Deserializing is the opposite and equal reaction of serialization (that makes sense so far) done with things like JSON.parse(), and transforms data back into real objects. Any time you deal with data sent over the internet or stored in a database, you’re doing serialization AND deserialization.

What do we use serialization for in Flask?

When you integrate SerializerMixin into your Flask-SQLalchemy model classes, you can make your models serializable, which in turn makes the data within them (as a blog I found says) “compact, platform-independent, and easy for platforms and humans to read!”

Note: In the example below, SerializerMixin is the first argument in the model class, and has been imported “offscreen” at the top of the file like so:

from sqlalchemy_serializer import SerializerMixin

How do we do serialization?

If you have an instance of a model class, you can use a method like to_json() or add rules into a to_dict (by saying instance.to_dict(rules=(“-excluded_thing”,))) to serialize on a more individual basis, but you can also serialize on a broader level first inside the class itself. When you define a model class, you can use a property like serialize_rules to specify which fields to exclude from the default serialized representation of the model. In this example, we’re defining a class and showing its one-to-many relationship with another, while also indicating via serialize_rules that we don’t want to see certain things returned in API calls. In this case, we’ll see the orders affiliated with each deli but not the other delis’ orders affiliated with those orders (which would be, in technical terms, a recursion hellscape.)

class Deli(db.Model, SerializerMixin):
            __tablename__ = 'delis'

            id = db.Column(db.Integer, primary_key=True)
            name = db.Column(db.String)
            signature_sandwich = db.Column(db.String)

            # Add relationship (deli has many customers through orders)
            orders = db.relationship(
                "Order", backref="deli", cascade="all, delete-orphan")

            # Add serialization rules
            serialize_rules = ("-orders.deli",)
Enter fullscreen mode Exit fullscreen mode

What’s that weird comma doing?

Serialization rules are tuples (aka a sequence of immutable objects separated by a comma and enclosed in parentheses) and tuples always have commas, even if there’s only one value in there. If you don’t include the comma, Flask-SQLAlchemy will raise a TypeError exception indicating that currently, serialize_rules isn’t a tuple, but it has to be.

Tyra Banks looking enraged, text says "You didn't put a comma in your serializer tuple!"

Why write serializer rules?

If you want to see customized parts of a data set, or avoid seeing recursive or redundant (or just unhelpful) portions of a data set, you’re gonna want to change how the data gets serialized. Check out the following example of models:

from flask_sqlalchemy import SQLAlchemy
        from sqlalchemy_serializer import SerializerMixin


        db = SQLAlchemy()

        class User(db.Model, SerializerMixin):
            __tablename__ = 'users'

            id = db.Column(db.Integer, primary_key=True)
            name = db.Column(db.String)
            email = db.Column(db.String, unique=True)

            # Add relationship (has many posts)
            posts = db.relationship("Post", backref="author")

            # Add serialization rules (don't show people's passwords!)
            serialize_rules = ("-password",)

        class Post(db.Model, SerializerMixin):
            __tablename__ = 'posts'

            id = db.Column(db.Integer, primary_key=True)
            title = db.Column(db.String)
            content = db.Column(db.Text)

            # Add relationship (belongs to one user)
            author_id = db.Column(db.Integer, db.ForeignKey('users.id'))

            # Add serialization rules (don't show comments by default)
            serialize_rules = ("-comments",)

        class Comment(db.Model, SerializerMixin):
            __tablename__ = 'comments'

            id = db.Column(db.Integer, primary_key=True)
            content = db.Column(db.Text)

            # Add relationship (belongs to one post)
            post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))

            # Add serialization rules (no rules)
            serialize_rules = ()

Enter fullscreen mode Exit fullscreen mode

In this example, we’re serializing specifics that are integral to privacy, like not seeing a password or comments affiliated with a post. You can also get a little more interesting with serialization to prevent users from seeing a recursive data spiral, like so:

from flask_sqlalchemy import SQLAlchemy
    from sqlalchemy_serializer import SerializerMixin


       db = SQLAlchemy()

    class Deli(db.Model, SerializerMixin):
         __tablename__ = 'delis'

         id = db.Column(db.Integer, primary_key=True)
         name = db.Column(db.String)
         signature_sandwich = db.Column(db.String)

         # Add relationship (deli has many customers through orders)
         orders = db.relationship(
         "Order", backref="deli", cascade="all, delete-orphan")

         # Add serialization rules (-orders.deli,)
         serialize_rules = ("-orders.deli",)

         # Add validation for name
         @validates('name')
         def validate_name(self, key, name):
             if name and len(name) >= 1:
                 return name
             else:
                 raise ValueError("Must have valid name attribute")


    class Customer(db.Model, SerializerMixin):
        __tablename__ = 'customers'

        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String)
        allergies = db.Column(db.String)

        # Add relationship (visits many delis through orders)
        orders = db.relationship(
        "Order", backref="customer", cascade="all, delete-orphan")

        # Add serialization rules (-orders.customer,)
        serialize_rules = ("-orders.customer",)

    class Order(db.Model, SerializerMixin):
        __tablename__ = 'orders'

        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String)

        # Add relationships (foreign keys for customer and deli)
        customer_id = db.Column(db.Integer, db.ForeignKey("customers.id"))
        deli_id = db.Column(db.Integer, db.ForeignKey("delis.id"))

        # Add serialization rules (deli.orders, customer.orders)
        serialize_rules = ("-deli.orders", "-customer.orders")

Enter fullscreen mode Exit fullscreen mode

How do we know what to put in the serialize_rules tuple?

So this is really cool and all, but how do we know what to put into the tuples? Is there a rule for the dot notation? What am I actually saying when I type ("-orders.customer",) or ("-deli.orders", "-customer.orders”)?

In the model class Customer that I defined above, the relationship is defined as being between Customer and Orders (the intermediate table.) So when you serialize to avoid the recursion spiral of doom, you only want to see the orders affiliated with a specific instance of a customer, and no other customers, even if they have the same order. That’s why the serializer rules for this class is ("-orders.customer”,). Without that dot notation, you could see that Customer A ordered a bacon-egg-and-cheese, but also a bunch of other customers who also ordered it, like Customer B, Customer C, and Customer D, which a) isn’t relevant to our data interests and b) makes Flask-SQLalchemy unhappy. So we code efficiently and throw a serialize rule in there to make sure we only see relevant information for that model.

On the intermediate table in the earlier example, Orders, the tuple has two excluded fields, ("-deli.orders", "-customer.orders”). This means that when we look for orders affiliated with a specific deli or customer, we won’t see the other orders that deli or customer has. This keeps our data specific, relevant, and non-recursive, and our code functional and free of the dreaded “Recursion Limit Exceeded” errors.

Tyra Banks wearing pearls, text says: "recursiononrecursiononrecursion"

Will serializer rules affect how data is stored in the database?

Nope! These rules don’t affect the database, just how the data is presented in your APIs. You’ll still be able to retrieve and sort through it in all its big messy glory, even if you use serialization rules to make it more organized on the backend.

What happens if we don’t serialize data in a RESTful API?

The short answer is: nothing good. Here are some examples of what can happen:

  • The client might not be able to parse the raw data. (This can cause crashes.)

  • The client might be exposed to sensitive information that should be protected. (This can lead to users getting sensitive information leaked or exposed)

  • The data might be too big to be transferred efficiently and take a while to load in the client

  • If the raw data isn’t formatted in a way the client can “understand” it might not be displayable at all.

image of J Alexander looking confused with text saying: "The client when your data should have been serialized"


Don’t want to see these things happen? Serialize, serialize, serialize.

Works cited:

  1. S. Stevens, “Flask+SQLAlchemy Serializer: A Comprehensive Guide to Serialization,” Medium, Aug. 08, 2023. https://medium.com/@seanstevens729/flask-sqlalchemy-serializer-a-comprehensive-guide-to-serialization-aec8eeb8b456#:~:text=Serialization%2C%20the%20process%20of%20converting (accessed Oct. 15, 2023).
  2. “Python Recursion Errors & Serializer,” DEV Community, Jun. 05, 2023. https://dev.to/jvaughn619/python-recursion-errors-serializer-2kh8 (accessed Oct. 15, 2023).
  3. S. radha krishnan, “Python Serialization and Deserialization in Flask SQLAlchemy,” Medium, Oct. 11, 2023. https://medium.com/@srimathiradhakrishna/python-serialization-and-deserialization-in-flask-sqlalchemy-c2ff09d69677 (accessed Oct. 15, 2023).
  4. “API — Flask Documentation (3.0.x),” flask.palletsprojects.com. https://flask.palletsprojects.com/en/3.0.x/api/

Top Model Memes:

  1. “Americas next top model,” Imgflip. https://imgflip.com/i/82ntne (accessed Oct. 15, 2023).
  2. “Tyra Banks,” Imgflip. https://imgflip.com/i/82nu69 (accessed Oct. 15, 2023).
  3. “Tyra Banks,” Imgflip. https://imgflip.com/i/82nuhn (accessed Oct. 15, 2023).
  4. “Miss J Alexander,” Imgflip. https://imgflip.com/i/82nyz5 (accessed Oct. 15, 2023).
💖 💪 🙅 🚩
varlotte
Charlotte Bush

Posted on October 15, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related