javex
javex

Reputation: 7564

Is __new__ a good way to retrieve a SQLAlchemy object

I am using SQLAlchemy and I just read about the __new__ function. I also read the other posts here about __new__ so I am aware of the difference to __init__, the order they get called and their purpose and the main message for me was: Use __new__ to control the creation of a new instance.

So with that in mind, when I work with SQLAlchemy and want to retrieve an instance (and create one if it does not already exist, e.g. retrieve a User object, I normally do this:

user = DBSession.query(User).filter(User.id==user_id).first()
if not user:
    user = User()

This would either return the current user or give me a new one. Now with my new knowledge about magic, I thought something like this could be a good idea:

user = User(id=user_id)

And in my database class, I would call:

def __new__(cls, id=0):
    if id:
        user = DBSession.query(User).filter(User.id==id).first()
    if not id or not user:
        user = super(User, cls).__new__(cls, id=id)
    return user

Now this code is only a quick draft (e.g. a call to super is missing) but it should clearly point out the idea.

Now my question: Is this a good practice or should I avoid this? If it should be avoided: Why?

Upvotes: 1

Views: 906

Answers (3)

BrenBarn
BrenBarn

Reputation: 251598

Based on your question and your comments, I would suggest you not do this, because it doesn't appear you have any reason to do so, and you don't seem to understand what you're doing.

You say that you will put certain code __new__. But in the __new__ of what? If you have this:

class User(Base):
    def __new__(cls, id=0):
        if id:
            user = DBSession.query(User).filter(User.id==id).first()
        if not user:
            user = User()
        return user

. . . then when you try to create a User instance, its __new__ will try to create another instance, and so on, leading to infinite recursion.

Using user = User.__init__() solves nothing. __init__ always returns None, so you will just be trying to create a None object.

The appropriate use case for __new__ is when you want to change what kind of object is returned when you instantiate a class by doing SomeClass(). It is rare to need to do this. The most common case is when you want to create a user-defined class that mimics a builtin type such as dict, but even then you might not need to do this.

If your code works without overriding __new__, don't override __new__. Only override it if you have a specific problem or task that can't be solved in another way.

Upvotes: 2

SingleNegationElimination
SingleNegationElimination

Reputation: 156308

There is a very distinct difference between the first example (checking the return value) and the second (using the constructor immediately); and that difference is the free variable: DBSession.

In some cases, this difference is not interesting; If you are only using your sqlalchemy mapped objects for database persistence; and then only in contexts where sqlalchemy.orm.scopedsession is permissible (exactly one session per thread). then the difference is not very interesting.

I have found it unusual for both of these conditions to hold, and often neither holds.

By doing this you are preventing the objects from being useful outside the context of database persistence. By disconnecting your models from the database, your application can answer questions like "what if this object had this attribute?" in addition to questions like "does this object have this attribute?" This gets to the crux of why we map database values as python objects, so that they can have interesting behaviors, instead of just as dicts, which are merely bags of attributes.

For instance, in addition to using a regular database persistent login; you might allow users to log into your site with something like OAuth. Although you don't need to persist the users' name and password to your database, you still need to create the User object for the rest of your application to work (so that the user's gravatar shows up in the template).

The other question of implicitly accessing a particular database context by default is usually a bad idea. As applications grow, the need to manage how a database gets more complicated. Objects may be partitioned across several database hosts; you may be managing several concurrent transactions in the same thread; you might want to reuse a particular session for caching performance reasons. The sqlalchemy Session class exists to address all of these peculiarities; managing them explicitly, even when you are just using the most common pattern; makes dealing with the occasional variation much easier.

A really common example of that in web apps is start-up code; Sometimes it's neccesary to pull some key bits of data out of the database before an application is ready to serve any requests; but since there is no request to serve, where does the database connection come from? How do you get rid of it once you've finished starting up? These questions are usually non-issues with explicitly managed sessions.

Upvotes: 0

schacki
schacki

Reputation: 9533

From what I see and unterstand, there is no reason why not to put your code into __init__ instead of __new__. There are only a few and very limited - but valid - uses cases for __new__ and you should really know what you are doing. So unless you have a very good reason, stick with __init__.

Upvotes: 0

Related Questions