Check for invalid property names on mapped objects

Issue #547 resolved
Former user created an issue

We developed this function when porting from an existing SQLObject project. Since SQLObject and SQLAlchemy use different property names, we developed a custom setattr function to throw an error on any attempt to set an unknown property name. This has proven to be quite valuable for us; would it be useful to include this behavior as an option in the core SQLAlchemy?

The code is below. As written, we used it in a common base class for all our objects but in the spirit of SQLAlchemy it could be patched into each mapped class instead.

(If you like the concept but think additional work is needed, you can contact me at barry at exchangeframe dot com.)

@classmethod
def _find_class_descriptor(class_, name):
    """Searches base classes for a descriptor by the specified name.
    Returns it if found, returns None otherwise.
    """
    # Search ourselves first
    if name in class_.__dict__:
        class_property = class_.__dict__[name](name)
        if hasattr(class_property, '__set__'):
            return class_property
    # Search base classes recursively. Note we don't follow the same order
    # as new-style classes. This will probably be okay.
    for base in class_.__bases__:
        if hasattr(base, '_find_class_descriptor'):
            parent_result = base._find_class_descriptor(name)
            if parent_result != None:
                return parent_result

    return None
    def __setattr__(self, name, value):
        """Only allows setting an attribute if it's already there or if it's one
        of the special SQLAlchemy attributes. This helps catch errors such
        as missing relations or backrefs.
        """
        # This handles the case of properties defined using Python's built-in
        # "property" feature, where setting the attribute goes through a
        # function. We handle properties defined on our class or any of
        # our parents.
        class_property = self._find_class_descriptor(name)
        if class_property:
            class_property.__set__(self, value)
            return
        # Allow setting pre-existing attributes, the special SQLAlchemy
        # properties.
        exists = hasattr(self, name)
        if exists or name.find('_sa_') != -1 or name.startswith('_AssociationProxy_') \
           or name in ('_entity_name', '_instance_key'):
            self.__dict__[name](name) = value
        else:
            raise AttributeError, 'Invalid attribute reference on %s: %s' % (self.__class__.__name__, name)

Comments (4)

  1. Mike Bayer repo owner

    you might want to see if the Elixir guys are interested in this. ActiveMapper is effectively replaced by Elixir.

    one thing to keep in mind with this approach is that many classes do need to store attributes which are not mapped.

  2. Former user Account Deleted

    Agreed, sometimes people need to store transient attributes. In the cases where we needed to do that, I used custom descriptors on my class so this function would not throw an error. I felt this was a small price to pay in exchange for the error checking we gained, especially since we were porting a large existing code base. Disclosure: I am a reformed C++ programmer. :-)

    Thanks, I will check with the Elixir guys.

  3. Log in to comment