views:

164

answers:

3

Hi there,

I'd like to override __deepcopy__ for a given SQLAlchemy-mapped class such that it ignores any SQLA attributes but deepcopies everything else that's part of the class.

I'm not particularly familiar with overriding any of Python's built-in objects in particular but I've got some idea as to what I want.

Let's just make a very simple class User that's mapped using SQLA.

class User(object):
    def __init__(self, user_id=None, name=None):
        self.user_id = user_id
        self.name = name

I've used dir() to see, before and after mapping, what SQLAlchemy-specific attributes there are and I've found _sa_class_manager and _sa_instance_state.

Questions
Provided those are the only ones how would I ignore that when defining __deepcopy__?
Also, are there any attributes the SQLA injects into the mapped object?


(I asked this in a previous question (as an edit a few days after I selected an answer to the main question, though) but I think I missed the train there. Apologies for that.)


Edit - Fixed code thanks to zifot's answer

The only thing I got out of the Python docs is that you need to define deepcopy with memo as an extra argument. After a teensy bit of digging around I tried this out:

def __deepcopy__(self, memo):
    dpcpy = self.__class__()
    memo[id(self)] = dpcpy
    for attr in dir(self):
        if not attr.startswith('_'):
            value = getattr(self, attr)
            setattr(dpcpy, attr, copy.deepcopy(value, memo))
    return dpcpy

Then I created an instance of User as:

snake = User(913, 'Snake,S.')  

After that, I tried a deepcopy operation as:

snake_dc = copy.deepcopy(snake)

...and snake_dc still has the SQLA attributes in it...

I'm all open to help, suggestions, etc.

A: 

I'm not an expert on deepcopy, but from the error it looks like you need a parameterless constructor to call self.__class__() without parameters.

mavnn
Sorry, what do you mean by "parameterless constructor"?
Az
Sorry. An `__init__` which requires no variables beyond self. See zifot's answer for an example.
mavnn
What he means is that your User.__init__ function ("constructor", although technically it's not a constructor) takes three arguments: self, user_id and name, but when you're calling self.__class__() you are passing only the first one (implicitly).
zifot
+1  A: 

mavnn is right. For example try change your init of User to:

def __init__(self, user_id = None, name = None):
        self.user_id = user_id
        self.name = name

As for copying mapped instances, I recommend reading this thread

zifot
Thanks. Changed the `__init__` for `User`, but unfortunately `snake_dc` seems to have the SQLA attributes intact :S
Az
That is which ones?
zifot
'_sa_class_manager', '_sa_instance_state'
Az
That's because User class is mapped and every new instance of it will have these attributes (in particular that created in __deepcopy__ by calling self.__class__()). You would have to remove mapping before creating an instance and then put it back. But I have a strong feeling that is not what you actually want to achieve. Don't you just want to have a new instance that can be later put to the sqlalchemy's session but is not related to "original" instance in a sql sense, that is doesn't have it's primary key?
zifot
Well basically, I'm invoking `deepcopy` to make copies of the object and make some changes to those copies. I don't need the SQLA attributes and deepcopying should keep the mapped version intact as well. So is there a way I can invoke `deepcopy` such that I'm only copying everything that, well, doesn't start with `'_'`?
Az
Well, as long as User class is mapped, every current and new instance of it will have these _sa.* attributes (my previous comment about removing mapper is incorrect).But why is that a problem exactly? That deep copy is not in sqlalchemy's session and you can modify it any way you want.
zifot
A: 

To exclude sqlalchemy columns and mapped attributes you would do something along the lines of:

for attr in dir(self):
    if not self._sa_class_manager.mapper.has_property(key):
        ...
Singletoned