Skip to main content

Saving users from debug-hell by conveniently keeping track of mutations in objects and logging them

Project description

Project description

Stay out of debug hell by letting the mutationtracker do the dirty work for you! It will ease up the process of identifying where an object took a wrong turn. Just don’t tell your employer, so they won’t fire half your colleagues due to the time saved on debugging.

Features

  • Hold all mutations in the MutationLedger in the MUTATIONS attribute of any child class of MutationTrackedObject or class decorated by track_mutations (includes full trace stack)
  • Log creation and mutations of object with log callable of your choice (e.g. print, logger.debug, logger.info)
  • If a MutationTrackedObject is set as attribute value to another MutationTrackedObject, the caller function of the 'filler' is saved in the filled_by attribute of the MutationLedger

Table of contents

How to install & use

Install this package easily using pip:

pip install mutationtracker

This project aims to be very easy to use. Import either the MutationTrackedObject or track_mutations decorator in your Python module. Next, define your custom class of which instances should be tracked, and optionally add a log callable to it:

from mutationtracker import MutationTrackedObject


class YourClass(MutationTrackedObject):
    _mutations_log_function = print  # optional attribute; might be other callable; default is no logging

    def __init__(self):
        ...

or

from mutationtracker import track_mutations


@track_mutations(log_function=print)  # optional keyword argument; might be other callable; default is no logging
class YourClass:
    def __init__(self):
        ...

Your class will automatically contain the MUTATIONS attribute holding the full mutation history of the instance. For an extensive enjoyable example, try code below.

Example 1: MutationTrackedObject base class

Code

This example is best shown using two files in a single directory: alice_and_bob.py and run.py. The latter is not mandatory, but will improve the result because it not ran as script. Let's create alice_and_bob.py first with the following contents:

from datetime import datetime, date
import time

from mutationtracker import MutationTrackedObject


class Person(MutationTrackedObject):

    _mutations_log_function = print  # optional attribute, remove for no console output

    def __init__(
        self,
        social_security_number,
        name,
        gender,
        date_of_birth,
        children,
        date_of_death=None,
    ):
        self.social_security_number = social_security_number
        self.name = name
        self.gender = gender
        self.date_of_birth = date_of_birth
        self.date_of_death = date_of_death
        self.children = children

    def have_child(
        self, social_security_number, name, gender, date_of_birth=datetime.now().date()
    ):
        baby = Person(social_security_number, name, gender, date_of_birth, [])
        self.children = self.children + [baby]

    def adopt_child(
        self, social_security_number, name, gender, date_of_birth=datetime.now().date()
    ):
        child = Person(social_security_number, name, gender, date_of_birth, [])
        self.children = self.children + [child]

    def __repr__(self):
        return f"{self.social_security_number} - {self.name}"


def main():
    #  ========== Example: init of Alice ==========
    print("========== Example: init of Alice ==========")
    example_person = Person(12345678901, "Alice", "Female", date(1990, 3, 7), [])

    #  ========== Example: children ==========
    print("\n========== Example: children ==========")
    #  might one day have children herself
    example_person.have_child(12312312301, "Charles", "Male")
    example_person.have_child(12312312302, "Davey", "Male")
    #  might also adopt one more
    example_person.adopt_child(
        99889988771, "Fatima", "Female", date_of_birth=date(2024, 2, 2)
    )
    #  might find out Charles is actually a different baby because of a hospital mix-up
    #  so put him up for adoption as he is not as enjoyable

    def get_rid_of_child(person, name):
        person.children = [c for c in person.children if c.name != name]

    get_rid_of_child(example_person, "Charles")

    #  ========== Example: gender ==========
    print("\n========== Example: gender ==========")
    #  is her gender right? might change

    def change_gender(person, new_name, new_gender):
        """Might one day find out something is off but that can be changed"""
        time.sleep(0.1)  # takes some time
        person.name = new_name
        person.gender = new_gender

    #  actually, she feels like he and continues as Bob
    change_gender(example_person, "Bob", "Male")
    #  Might regret that later, and actually convert back
    change_gender(example_person, "Alice", "Female")

    #  ========== Example: safari trip gone wrong ==========
    print("\n========== Example: safari trip gone wrong ==========")
    #  live is an adventure, so go on a nice safari trip

    def go_on_safari(person):
        """Go on a nice and safe safari trip, so better be careful, right?"""
        pet_lion(person)

    def pet_lion(person):
        """Do not try this at home, or at the zoo, or while on safari, or anywhere ever, it might end badly"""
        person.date_of_death = datetime.now().date()

    go_on_safari(example_person)

    #  ========== Example: log full history of Alice ==========
    print("\n========== Example: log full history of Alice ==========")
    example_person.log_all_mutations()

    return example_person


if __name__ == "__main__":
    main()

Next up, you might want to create run.py to prevent it from running as script:

from alice_and_bob import main

"""
This example shows the Person class with baseclass MutationTrackedObject, and tells the life story of instance Alice

Make sure to put an breakpoint on the line with the `print` function and take a look at
`example_person.MUTATIONS` for the full experience provided by the MutationTrackedObject baseclass
"""
example_person = main()

# place a breakpoint on the print() line below
# run it in debug mode
# and make sure to check out example_person.MUTATIONS for the full experience
print("Thank you for trying the example with the baseclass MutationTrackedObject!")

Output

========== Example: init of Alice ==========
A new Person instance is being created by examples.baseclass.alice_and_bob.main:46: args=(12345678901, 'Alice', 'Female', datetime.date(1990, 3, 7), []), kwargs={}
Change in Person instance (repr unavailable) attribute social_security_number: to 12345678901 from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:20)
Change in Person instance (repr unavailable) attribute name: to Alice from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:21)
Change in Person instance (12345678901 - Alice) attribute gender: to Female from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:22)
Change in Person instance (12345678901 - Alice) attribute date_of_birth: to 1990-03-07 from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:23)
Change in Person instance (12345678901 - Alice) attribute date_of_death: to None from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:24)
Change in Person instance (12345678901 - Alice) attribute children: to [] from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:25)

========== Example: children ==========
A new Person instance is being created by examples.baseclass.alice_and_bob.Person.have_child:30: args=(12312312301, 'Charles', 'Male', datetime.date(2025, 3, 16), []), kwargs={}
Change in Person instance (repr unavailable) attribute social_security_number: to 12312312301 from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:20)
Change in Person instance (repr unavailable) attribute name: to Charles from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:21)
Change in Person instance (12312312301 - Charles) attribute gender: to Male from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:22)
Change in Person instance (12312312301 - Charles) attribute date_of_birth: to 2025-03-16 from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:23)
Change in Person instance (12312312301 - Charles) attribute date_of_death: to None from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:24)
Change in Person instance (12312312301 - Charles) attribute children: to [] from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:25)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles] from [] (by examples.baseclass.alice_and_bob.Person.have_child:31)
A new Person instance is being created by examples.baseclass.alice_and_bob.Person.have_child:30: args=(12312312302, 'Davey', 'Male', datetime.date(2025, 3, 16), []), kwargs={}
Change in Person instance (repr unavailable) attribute social_security_number: to 12312312302 from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:20)
Change in Person instance (repr unavailable) attribute name: to Davey from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:21)
Change in Person instance (12312312302 - Davey) attribute gender: to Male from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:22)
Change in Person instance (12312312302 - Davey) attribute date_of_birth: to 2025-03-16 from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:23)
Change in Person instance (12312312302 - Davey) attribute date_of_death: to None from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:24)
Change in Person instance (12312312302 - Davey) attribute children: to [] from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:25)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles, 12312312302 - Davey] from [12312312301 - Charles] (by examples.baseclass.alice_and_bob.Person.have_child:31)
A new Person instance is being created by examples.baseclass.alice_and_bob.Person.adopt_child:36: args=(99889988771, 'Fatima', 'Female', datetime.date(2024, 2, 2), []), kwargs={}
Change in Person instance (repr unavailable) attribute social_security_number: to 99889988771 from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:20)
Change in Person instance (repr unavailable) attribute name: to Fatima from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:21)
Change in Person instance (99889988771 - Fatima) attribute gender: to Female from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:22)
Change in Person instance (99889988771 - Fatima) attribute date_of_birth: to 2024-02-02 from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:23)
Change in Person instance (99889988771 - Fatima) attribute date_of_death: to None from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:24)
Change in Person instance (99889988771 - Fatima) attribute children: to [] from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:25)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles, 12312312302 - Davey, 99889988771 - Fatima] from [12312312301 - Charles, 12312312302 - Davey] (by examples.baseclass.alice_and_bob.Person.adopt_child:37)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312302 - Davey, 99889988771 - Fatima] from [12312312301 - Charles, 12312312302 - Davey, 99889988771 - Fatima] (by examples.baseclass.alice_and_bob.get_rid_of_child:61)

========== Example: gender ==========
Change in Person instance (12345678901 - Alice) attribute name: to Bob from Alice (by examples.baseclass.alice_and_bob.change_gender:72)
Change in Person instance (12345678901 - Bob) attribute gender: to Male from Female (by examples.baseclass.alice_and_bob.change_gender:73)
Change in Person instance (12345678901 - Bob) attribute name: to Alice from Bob (by examples.baseclass.alice_and_bob.change_gender:72)
Change in Person instance (12345678901 - Alice) attribute gender: to Female from Male (by examples.baseclass.alice_and_bob.change_gender:73)

========== Example: safari trip gone wrong ==========
Change in Person instance (12345678901 - Alice) attribute date_of_death: to 2025-03-16 from None (by examples.baseclass.alice_and_bob.pet_lion:90)

========== Example: log full history of Alice ==========
===== All mutations for Person instance 12345678901 - Alice =====
Created by: examples.baseclass.alice_and_bob.main:46
Last mutation by: examples.baseclass.alice_and_bob.pet_lion:90
Filled by: None
Number of attribute mutations: 15
Change in Person instance (12345678901 - Alice) attribute social_security_number: to 12345678901 from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:20)
Change in Person instance (12345678901 - Alice) attribute name: to Alice from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:21)
Change in Person instance (12345678901 - Alice) attribute gender: to Female from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:22)
Change in Person instance (12345678901 - Alice) attribute date_of_birth: to 1990-03-07 from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:23)
Change in Person instance (12345678901 - Alice) attribute date_of_death: to None from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:24)
Change in Person instance (12345678901 - Alice) attribute children: to [] from AttributeDoesNotExist (by examples.baseclass.alice_and_bob.Person.__init__:25)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles] from [] (by examples.baseclass.alice_and_bob.Person.have_child:31)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles, 12312312302 - Davey] from [12312312301 - Charles] (by examples.baseclass.alice_and_bob.Person.have_child:31)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles, 12312312302 - Davey, 99889988771 - Fatima] from [12312312301 - Charles, 12312312302 - Davey] (by examples.baseclass.alice_and_bob.Person.adopt_child:37)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312302 - Davey, 99889988771 - Fatima] from [12312312301 - Charles, 12312312302 - Davey, 99889988771 - Fatima] (by examples.baseclass.alice_and_bob.get_rid_of_child:61)
Change in Person instance (12345678901 - Alice) attribute name: to Bob from Alice (by examples.baseclass.alice_and_bob.change_gender:72)
Change in Person instance (12345678901 - Alice) attribute gender: to Male from Female (by examples.baseclass.alice_and_bob.change_gender:73)
Change in Person instance (12345678901 - Alice) attribute name: to Alice from Bob (by examples.baseclass.alice_and_bob.change_gender:72)
Change in Person instance (12345678901 - Alice) attribute gender: to Female from Male (by examples.baseclass.alice_and_bob.change_gender:73)
Change in Person instance (12345678901 - Alice) attribute date_of_death: to 2025-03-16 from None (by examples.baseclass.alice_and_bob.pet_lion:90)
Thank you for trying the example with the baseclass MutationTrackedObject!

Debug mode

Feel free to experience it yourself in debug mode to explore the data in example_person.MUTATIONS near the end of the example script. There's more data than printed in the log, such as the full trace stack.

Example 2: track_mutations decorator

Code

This example is best shown using two files in a single directory: alice_and_bob.py and run.py. The latter is not mandatory, but will improve the result because it not ran as script. Let's create alice_and_bob.py first with the following contents:

from datetime import datetime, date
import time

from mutationtracker import track_mutations


@track_mutations(log_function=print)  # optional keyword argument, default: no output
class Person:
    def __init__(
        self,
        social_security_number,
        name,
        gender,
        date_of_birth,
        children,
        date_of_death=None,
    ):
        self.social_security_number = social_security_number
        self.name = name
        self.gender = gender
        self.date_of_birth = date_of_birth
        self.date_of_death = date_of_death
        self.children = children

    def have_child(
        self, social_security_number, name, gender, date_of_birth=datetime.now().date()
    ):
        baby = Person(social_security_number, name, gender, date_of_birth, [])
        self.children = self.children + [baby]

    def adopt_child(
        self, social_security_number, name, gender, date_of_birth=datetime.now().date()
    ):
        child = Person(social_security_number, name, gender, date_of_birth, [])
        self.children = self.children + [child]

    def __repr__(self):
        return f"{self.social_security_number} - {self.name}"


def main():
    #  ========== Example: init of Alice ==========
    print("========== Example: init of Alice ==========")
    example_person = Person(12345678901, "Alice", "Female", date(1990, 3, 7), [])

    #  ========== Example: children ==========
    print("\n========== Example: children ==========")
    #  might one day have children herself
    example_person.have_child(12312312301, "Charles", "Male")
    example_person.have_child(12312312302, "Davey", "Male")
    #  might also adopt one more
    example_person.adopt_child(
        99889988771, "Fatima", "Female", date_of_birth=date(2024, 2, 2)
    )
    #  might find out Charles is actually a different baby because of a hospital mix-up
    #  so put him up for adoption as he is not as enjoyable

    def get_rid_of_child(person, name):
        person.children = [c for c in person.children if c.name != name]

    get_rid_of_child(example_person, "Charles")

    #  ========== Example: gender ==========
    print("\n========== Example: gender ==========")
    #  is her gender right? might change

    def change_gender(person, new_name, new_gender):
        """Might one day find out something is off but that can be changed"""
        time.sleep(0.1)  # takes some time
        person.name = new_name
        person.gender = new_gender

    #  actually, she feels like he and continues as Bob
    change_gender(example_person, "Bob", "Male")
    #  Might regret that later, and actually convert back
    change_gender(example_person, "Alice", "Female")

    #  ========== Example: safari trip gone wrong ==========
    print("\n========== Example: safari trip gone wrong ==========")
    #  live is an adventure, so go on a nice safari trip

    def go_on_safari(person):
        """Go on a nice and safe safari trip, so better be careful, right?"""
        pet_lion(person)

    def pet_lion(person):
        """Do not try this at home, or at the zoo, or while on safari, or anywhere ever, it might end badly"""
        person.date_of_death = datetime.now().date()

    go_on_safari(example_person)

    #  ========== Example: log full history of Alice ==========
    print("\n========== Example: log full history of Alice ==========")
    example_person.log_all_mutations()

    return example_person


if __name__ == "__main__":
    main()

Next up, you might want to create run.py to prevent it from running as script:

from alice_and_bob import main

"""
This example shows the Person class with decorator track_mutations, and tells the life story of instance Alice

Make sure to put an breakpoint on the line with the `print` function and take a look at
`example_person.MUTATIONS` for the full experience provided by the track_mutations decorator
"""
example_person = main()


# place a breakpoint on the print() line below
# run it in debug mode
# and make sure to check out example_person.MUTATIONS for the full experience
print("Thank you for trying the example with the decorator track_mutations!")

Output

========== Example: init of Alice ==========
A new Person instance is being created by examples.decorator.alice_and_bob.main:44: args=(12345678901, 'Alice', 'Female', datetime.date(1990, 3, 7), []), kwargs={}
Change in Person instance (repr unavailable) attribute social_security_number: to 12345678901 from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:18)
Change in Person instance (repr unavailable) attribute name: to Alice from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:19)
Change in Person instance (12345678901 - Alice) attribute gender: to Female from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:20)
Change in Person instance (12345678901 - Alice) attribute date_of_birth: to 1990-03-07 from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:21)
Change in Person instance (12345678901 - Alice) attribute date_of_death: to None from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:22)
Change in Person instance (12345678901 - Alice) attribute children: to [] from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:23)

========== Example: children ==========
A new Person instance is being created by examples.decorator.alice_and_bob.Person.have_child:28: args=(12312312301, 'Charles', 'Male', datetime.date(2025, 3, 16), []), kwargs={}
Change in Person instance (repr unavailable) attribute social_security_number: to 12312312301 from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:18)
Change in Person instance (repr unavailable) attribute name: to Charles from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:19)
Change in Person instance (12312312301 - Charles) attribute gender: to Male from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:20)
Change in Person instance (12312312301 - Charles) attribute date_of_birth: to 2025-03-16 from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:21)
Change in Person instance (12312312301 - Charles) attribute date_of_death: to None from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:22)
Change in Person instance (12312312301 - Charles) attribute children: to [] from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:23)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles] from [] (by examples.decorator.alice_and_bob.Person.have_child:29)
A new Person instance is being created by examples.decorator.alice_and_bob.Person.have_child:28: args=(12312312302, 'Davey', 'Male', datetime.date(2025, 3, 16), []), kwargs={}
Change in Person instance (repr unavailable) attribute social_security_number: to 12312312302 from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:18)
Change in Person instance (repr unavailable) attribute name: to Davey from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:19)
Change in Person instance (12312312302 - Davey) attribute gender: to Male from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:20)
Change in Person instance (12312312302 - Davey) attribute date_of_birth: to 2025-03-16 from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:21)
Change in Person instance (12312312302 - Davey) attribute date_of_death: to None from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:22)
Change in Person instance (12312312302 - Davey) attribute children: to [] from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:23)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles, 12312312302 - Davey] from [12312312301 - Charles] (by examples.decorator.alice_and_bob.Person.have_child:29)
A new Person instance is being created by examples.decorator.alice_and_bob.Person.adopt_child:34: args=(99889988771, 'Fatima', 'Female', datetime.date(2024, 2, 2), []), kwargs={}
Change in Person instance (repr unavailable) attribute social_security_number: to 99889988771 from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:18)
Change in Person instance (repr unavailable) attribute name: to Fatima from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:19)
Change in Person instance (99889988771 - Fatima) attribute gender: to Female from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:20)
Change in Person instance (99889988771 - Fatima) attribute date_of_birth: to 2024-02-02 from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:21)
Change in Person instance (99889988771 - Fatima) attribute date_of_death: to None from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:22)
Change in Person instance (99889988771 - Fatima) attribute children: to [] from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:23)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles, 12312312302 - Davey, 99889988771 - Fatima] from [12312312301 - Charles, 12312312302 - Davey] (by examples.decorator.alice_and_bob.Person.adopt_child:35)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312302 - Davey, 99889988771 - Fatima] from [12312312301 - Charles, 12312312302 - Davey, 99889988771 - Fatima] (by examples.decorator.alice_and_bob.get_rid_of_child:59)

========== Example: gender ==========
Change in Person instance (12345678901 - Alice) attribute name: to Bob from Alice (by examples.decorator.alice_and_bob.change_gender:70)
Change in Person instance (12345678901 - Bob) attribute gender: to Male from Female (by examples.decorator.alice_and_bob.change_gender:71)
Change in Person instance (12345678901 - Bob) attribute name: to Alice from Bob (by examples.decorator.alice_and_bob.change_gender:70)
Change in Person instance (12345678901 - Alice) attribute gender: to Female from Male (by examples.decorator.alice_and_bob.change_gender:71)

========== Example: safari trip gone wrong ==========
Change in Person instance (12345678901 - Alice) attribute date_of_death: to 2025-03-16 from None (by examples.decorator.alice_and_bob.pet_lion:88)

========== Example: log full history of Alice ==========
===== All mutations for Person instance 12345678901 - Alice =====
Created by: examples.decorator.alice_and_bob.main:44
Last mutation by: examples.decorator.alice_and_bob.pet_lion:88
Filled by: None
Number of attribute mutations: 15
Change in Person instance (12345678901 - Alice) attribute social_security_number: to 12345678901 from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:18)
Change in Person instance (12345678901 - Alice) attribute name: to Alice from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:19)
Change in Person instance (12345678901 - Alice) attribute gender: to Female from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:20)
Change in Person instance (12345678901 - Alice) attribute date_of_birth: to 1990-03-07 from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:21)
Change in Person instance (12345678901 - Alice) attribute date_of_death: to None from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:22)
Change in Person instance (12345678901 - Alice) attribute children: to [] from AttributeDoesNotExist (by examples.decorator.alice_and_bob.Person.__init__:23)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles] from [] (by examples.decorator.alice_and_bob.Person.have_child:29)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles, 12312312302 - Davey] from [12312312301 - Charles] (by examples.decorator.alice_and_bob.Person.have_child:29)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312301 - Charles, 12312312302 - Davey, 99889988771 - Fatima] from [12312312301 - Charles, 12312312302 - Davey] (by examples.decorator.alice_and_bob.Person.adopt_child:35)
Change in Person instance (12345678901 - Alice) attribute children: to [12312312302 - Davey, 99889988771 - Fatima] from [12312312301 - Charles, 12312312302 - Davey, 99889988771 - Fatima] (by examples.decorator.alice_and_bob.get_rid_of_child:59)
Change in Person instance (12345678901 - Alice) attribute name: to Bob from Alice (by examples.decorator.alice_and_bob.change_gender:70)
Change in Person instance (12345678901 - Alice) attribute gender: to Male from Female (by examples.decorator.alice_and_bob.change_gender:71)
Change in Person instance (12345678901 - Alice) attribute name: to Alice from Bob (by examples.decorator.alice_and_bob.change_gender:70)
Change in Person instance (12345678901 - Alice) attribute gender: to Female from Male (by examples.decorator.alice_and_bob.change_gender:71)
Change in Person instance (12345678901 - Alice) attribute date_of_death: to 2025-03-16 from None (by examples.decorator.alice_and_bob.pet_lion:88)
Thank you for trying the example with the decorator track_mutations!

Debug mode

Feel free to experience it yourself in debug mode to explore the data in example_person.MUTATIONS near the end of the example script. There's more data than printed in the log, such as the full trace stack.

Limitations

  • The mutation will keep the new_value as it was at the time of mutation (using deepcopy). If it is a mutable type, it might be further mutated bypassing the observer functionality because __setattr__ is not called.
  • The user is prohibited from redefining __setattr__ and __new__ while using MutationTrackedObject because it will break the observer functionality. If this is blocking for your use, please use the track_mutations decorator instead.
  • The MutationTrackedObject functionality will inherit to all child classes of the class actually aimed to be tracked. Often this is desirable, but it might be undesirable in some cases. If that's the case, please use the track_mutations decorator instead.

Release notes

No changes since initial release v1.0.1

Useful links

License

Copyright (c) 2025 Jaime Derrez
All rights reserved.

This code is available under the BSD-license. Read the LICENSE file in the root directory for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mutationtracker-1.0.1.tar.gz (17.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mutationtracker-1.0.1-py3-none-any.whl (14.5 kB view details)

Uploaded Python 3

File details

Details for the file mutationtracker-1.0.1.tar.gz.

File metadata

  • Download URL: mutationtracker-1.0.1.tar.gz
  • Upload date:
  • Size: 17.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for mutationtracker-1.0.1.tar.gz
Algorithm Hash digest
SHA256 6cfd5cefe9b5e5d5af2cf9003d8ad6a0dbfcf118e83aa5c2fd84237a8f2b5567
MD5 562116b7ee073678693095c085f1d639
BLAKE2b-256 6251132460eff060143f3cf19beafb025cd7ed41666e8e7a1b234d424ea65975

See more details on using hashes here.

Provenance

The following attestation bundles were made for mutationtracker-1.0.1.tar.gz:

Publisher: python-publish.yml on RedmanLabs/mutationtracker

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mutationtracker-1.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for mutationtracker-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 80e2fa29ca083c203e5cfcc4c312d543bf18baaf2f037ededa144de7fccd17c3
MD5 249dedead6b1315f1259e62698bab7b6
BLAKE2b-256 31c885e565c7ecd7d2549236115bce057a8b32ef513877137aa9ba015702ef6f

See more details on using hashes here.

Provenance

The following attestation bundles were made for mutationtracker-1.0.1-py3-none-any.whl:

Publisher: python-publish.yml on RedmanLabs/mutationtracker

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page