JackOfAll
JackOfAll

Reputation: 120

Python inheritance best practice for 'object histories'

I have a number of classes (e.g. Contact) for which I also want to track the change history. Below is my starting point. I imagine this is a common use case, so would appreciate guidance on best practice here. Would this be a good place to use multiple inheritance, and what would that look like?

from datetime import date

class ContactHistory(object):
    def __init__(self, contact, change_action, change_user_id, change_source):
        self.name = contact.name
        self.phone = contact.phone
        self.email = contact.email
        self.change_action = change_action
        self.change_user_id = change_user_id
        self.change_source = change_source
        self.change_date = date.today()

    def __repr__(self):
        return '%s, %s, %s, %s, %s, %s, %s' % (self.name, self.email, self.phone, self.change_action, self.change_user_id, self.change_source, self.change_date)

class Contact(object):
    def __init__(self, name, phone, email, change_user_id, change_source):
        self.name = name
        self.phone = phone
        self.email = email
        self.history = []       
        self.history.append(ContactHistory(self, 'created', change_user_id, change_source))

    def update_phone(self, phone, change_user_id, change_source):
        self.phone = phone
        self.history.append(ContactHistory(self, 'phone updated', change_user_id, change_source))

    def get_history(self):
        return self.history

contact = Contact('Bill', '214-555-1212', 'me', 'admin page')
contact.update('972-555-1212', 'me', 'contact management page')
print contact.get_history()

Upvotes: 2

Views: 201

Answers (1)

Nolen Royalty
Nolen Royalty

Reputation: 18653

Here's one way you could use inheritance to approach this. ChangeableObject could be inherited by any object that needed to be changeable, with that object just describing the parameters that are used to create a shallow clone for history:

from collections import namedtuple

Change = namedtuple("Change", ("old", "new", "action"))

class History(object):
    def __init__(self):
        self.history = []

    def save_change(self, old, new, action):
        change = Change(old, new, action)
        self.history.append(change)

    def get_history(self):
        return self.history

class ChangeableObject(object):
    def __init__(self, make_history=True):
        if make_history:
            self.history = History()
            self.history.save_change(None, self, "created")
        self.cloneable_attributes = ()

    @classmethod
    def get_clone(cls, obj):
        attrs = {attr: getattr(obj, attr) for attr in obj.cloneable_attributes}
        return cls(make_history=False, **attrs)

    def view_history(self):
        return self.history.get_history()

class Contact(ChangeableObject):
    def __init__(self, name, phone, email, make_history=True):
        super(Contact, self).__init__(make_history=make_history)
        self.name = name
        self.phone = phone
        self.email = email
        self.cloneable_attributes = ("name", "phone", "email")

    def update_phone(self, phone):
        clone = self.get_clone(self)
        self.phone = phone
        self.history.save_change(clone, self, "phone updated")

    def __repr__(self):
        return "{} {} {}".format(self.name, self.phone, self.email)

With example code:

c = Contact("me", "123-123-123", "[email protected]")
c.update_phone("456-456-456")
print c
for i, hist in enumerate(c.view_history()):
    print "{}. {}".format(i, hist)

Output:

me 456-456-456 [email protected]
0. Change(old=None, new=me 456-456-456 [email protected], action='created')
1. Change(old=me 123-123-123 [email protected], new=me 456-456-456 [email protected], action='phone updated')

Upvotes: 1

Related Questions