Code Monkey home page Code Monkey logo

xobject's Introduction


xobject

Track mutations in python objects.

Warning

Not fully featured yet. Until version 1.0.0, API should be considered unstable.

What's this all about?

Establishing a save point for python objects and identifying which changes have occurred since it can be super useful. For example, someone may wish to discard all the modifications and reload the object state from save point. Others can try to perform some optimizations by adopting a lazy approach and just triggering actions after a save point is created, based only on the changed properties.

This project provides base classes and decorators to enable these features, with minimal requirement. xobject also provides a event-emitter mechanism (a synchronous pub/sub variation of the observer pattern) to monitor changes and perform custom actions before and after they take place.

Installation

sudo pip install xobject
# drop sudo if you are using a local installation or virtualenv-like tools

Quickstart

The example below shows a minimal xobject usage:

from __future__ import print_function
from xobject import XObject

class BankAccount(XObject):
   def __init__(self, owner):
       self.internal_state = {'owner': owner, 'balance': 0}
       super(User, self).__init__()

   def withdraw(self, amount):
       self.internal_state['balance'] -= amount
       return amount

   def deposit(self, amount):
       self.internal_state['balance'] += amount

   @property
   def balance(self):
       return self.internal_state['balance']

account = BankAccount('John Doe')

account.deposit(300)
account.is_dirty
# => True
account.enumerate_changes()
# => {update: {'balance': 300}}
account.commit()
account.is_dirty
# => False
account.enumerate_changes()
# => {}

account.withdraw(250)
account.enumerate_changes()
# => {update: {'balance': 50}}
account.rollback()
account.balance
# => 300
account.is_dirty
# => False

In order to subclass XObject, it is necessary to define a internal_state property, which value must be a primitive python dict (by default, XObject uses and empty dict if internal_state not defined). The method commit can be then used to save the object state and consolidate the last changes. Before calling commit, changes can be monitored using enumerate_changes method or the is_dirty property. There is also a rollback method that discard all the uncommitted changes and returns the object to the previous state.

It is important to always keep internal_state up-to-date. For complex usages, a dynamic property may be suitable:

@property
def internal_state(self):
    ...
    # returns a python primitive dict

@internal_state.setter
def internal_state(self, state_dict):
    ...
    # store the state

Note

Internally XObject uses jsondiff package with explicit syntax to enumerate changes.

Sentinel

A XObject instance has an especial sentinel property that implements the Event Emmiter pattern and is used to monitor the object life cycle. By default, the following events can be monitored:

Event Name Handler Arguments
will_commit instance, saved_state, pendding_state
did_commit instance, current_state
will_rollback instance, saved_state, pendding_state
did_rollback instance, current_state
def print_transition(account, old_state, new_state):
   old_balance = old_state['balance']
   new_balance = new_state['balance']
   print('balance will change from {} to {}', old_balance, new_balance)

def print_balance(account, state):
   print('current ballance: ', state['balance'])

account.sentinel.on('will_commit', print_transition)
account.sentinel.on('did_commit', print_balance)
money = account.withdraw(100)
account.commit()
# balance will change from 300 to 200
# current ballance: 200

Sentinel events can be disabled/enabled by calling the silence method:

account.sentinel.silence()  # disable events
...  # perform some actions
account.sentinel.silence(False)  # re-enable events

xproperties

The xproperties class decorator is a convenience tool that accepts any number of string parameters and define them as tracked properties. The XObject constructor will automatically accept keyword arguments for xproperties and set the initial internal state accordingly.

from xobject import XObject, xproperties

@xproperties('name', 'email')
class User(XObject):
   pass

john = User(name='John Doe', email='[email protected]')
john.email
# => '[email protected]'
john.name = 'John Smith'
john.enumerate_changes()
# => {update: {'name': 'John Smith'}}

For each property, the following events will be automatically triggered:

Event Name Handler Arguments
<property_name>:will_access instance
<property_name>:did_access instance, current_value
<property_name>:will_change instance, new_value
<property_name>:did_change instance, new_value

Additionally, it is possible to track custom computed properties using the xproperty decorator:

class ObjectWithProperties(XObject):
    @xproperty
    def custom_property(self):
        ...  # returns the computed value

    @custom_property.setter
    def custom_property(self, value):
        ...  # store the computed value

The overall effect of this decorator is wrap the function call with event triggers. The previous example is roughly equivalent to:

class ObjectWithProperties(XObject):
    @property
    def custom_property(self):
        self.sentinel.emit('custom_property:will_access', self)
        value = ...  # custom computation
        self.sentinel.emit('custom_property:did_access', self, value)
        return value

    @custom_property.setter
    def custom_property(self, value):
        self.sentinel.emit('custom_property:will_change', self, value)
        ...  # compute and store the new value
        self.sentinel.emit('custom_property:did_change', self, value)

Note that using xproperties is equivalent to calling xproperty decorator for each argument:

@xproperties('some_property', ...)
class SomeObject(XObject):
    pass

# is equivalent to:

class SomeObject(XObject):
    @xproperty
    def some_property(self, value):
        # just access internal dict
        return self.internal_state['custom_property']

    @custom_property.setter
    def some_property(self, value):
        # just store the new value in the internal dict
        self.internal_state['custom_property'] = value

    ...

Transactions

Instances of XObject class are also equipped with the tansaction context manager. Using this method, it is possible to perform operations in batch without all the intermediate event triggers. The following codes are equivalent:

with user.transaction():
    ...
    user.name = 'Fulano da Silva'
    ...
try:
    user.sentinel.silence()
    try:
        ...
        user.name = 'Fulano da Silva'
        ...
    finally:
        user.sentinel.silence(False)
except:
    user.rollback()
    raise
else:
    user.commit()

Stuff Doesn't Work

Any feedback you can give me on this would be gratefully received (see section Reporting a Bug at Contribution Guidelines.).

Can I help?

Yes, please! Contributions of any kind are welcome, and also feel free to ask your questions!

Please take a look at the Contribution Guidelines.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.