2025, Oct 23 11:00

Fix SQLAlchemy 'NoneType' lower() errors in Flask by computing Gravatar hashes after init

Diagnose and fix Flask SQLAlchemy errors like AttributeError: 'NoneType' has no attribute 'lower' in Gravatar hashing. Learn safe timing and init-based fixes.

Adding a new user in a Flask application suddenly starts throwing AttributeError: 'NoneType' object has no attribute 'lower'? If your model derives a Gravatar hash from the email and you rely on SQLAlchemy attribute events to trigger that calculation, the failure likely comes from when the event fires, not from the hash function itself.

Reproducing the issue

The pattern looks roughly like this: a model declares an email column and an avatar hash, computes the hash in a helper method, and wires a listener to the column’s set event to keep the hash in sync when the email is assigned.

class Account(db.Model):
    __tablename__ = 'accounts'
    id = db.Column(db.Integer, primary_key=True)
    contact_email = db.Column(db.String(128), unique=True, index=True)
    avatar_digest = db.Column(db.String(32))

    def __init__(self, **fields):
        super(Account, self).__init__(**fields)
        # ... other initialization

    def compute_avatar(self):
        return hashlib.md5(self.contact_email.lower().encode('utf-8')).hexdigest()

    @staticmethod
    def handle_email_set(target, value, oldvalue, initiator):
        # Fails here when target.contact_email is still None
        target.avatar_digest = target.compute_avatar()


db.event.listen(Account.contact_email, 'set', Account.handle_email_set)

Instantiating the model with contact_email set appears straightforward, yet the first access to lower() comes from inside the event handler and crashes because contact_email is still None at that moment.

What actually goes wrong

The error isn’t about hashing or string handling. It’s about timing. The set event on the mapped attribute fires while the object is still being initialized. When the listener runs and calls the hash helper, the attribute on the instance has not yet been assigned to the value you passed in, so the method reads None and attempts to call lower() on it. That’s where the AttributeError originates.

Fixing the behavior

The simplest way to avoid the race is to determine the avatar hash after the object has been constructed and the email is definitely available. That means moving the assignment out of the listener and into the constructor, guarded by a sanity check.

class Account(db.Model):
    __tablename__ = 'accounts'
    id = db.Column(db.Integer, primary_key=True)
    contact_email = db.Column(db.String(128), unique=True, index=True)
    avatar_digest = db.Column(db.String(32))

    def __init__(self, **fields):
        super(Account, self).__init__(**fields)
        # After fields are bound, compute the derived value if possible
        if self.contact_email is not None and self.avatar_digest is None:
            self.avatar_digest = self.compute_avatar()

    def compute_avatar(self):
        return hashlib.md5(self.contact_email.lower().encode('utf-8')).hexdigest()

If you still need to re-derive the hash when the email changes elsewhere in your code, protect the helper so it doesn’t assume the attribute is always present. This mirrors the practical safeguard applied in the working setup.

class Account(db.Model):
    __tablename__ = 'accounts'
    id = db.Column(db.Integer, primary_key=True)
    contact_email = db.Column(db.String(128), unique=True, index=True)
    avatar_digest = db.Column(db.String(32))

    def __init__(self, **fields):
        super(Account, self).__init__(**fields)
        if self.contact_email is not None and self.avatar_digest is None:
            self.avatar_digest = self.compute_avatar()

    def compute_avatar(self):
        if self.contact_email is None:
            return None
        return hashlib.md5(self.contact_email.lower().encode('utf-8')).hexdigest()

When relying on attribute events, be aware that listening to changes at this point is not suitable for this specific attribute if the handler expects the value to be set on the instance. Triggering the calculation post-initialization avoids the NoneType access.

Why this matters

ORM lifecycle hooks are powerful, but they can fire earlier than expected. Treating column events as if the instance has already been fully populated leads to fragile code and sporadic breakage that only shows up on new object creation. Understanding when SQLAlchemy dispatches events helps you decide whether to calculate derived fields in __init__, in explicit setters, or in a place where the data is guaranteed to exist.

Takeaways

Make derived values depend on states you control. If a value needs the email, compute it after the constructor binds keyword arguments. If you still need re-computation later, add a defensive check in the helper so it tolerates a transient None. And when a bug isn’t obvious, isolate the failing path to a minimal snippet that shows exactly when the attribute becomes available.

The article is based on a question from StackOverflow by David Scott and an answer by Detlef.