Metadata-Version: 2.1
Name: recollection
Version: 0.9.1
Summary: A python package exposing the memento design pattern
Home-page: https://github.com/mikemalinowski/recollection
Author: Mike Malinowski
Author-email: mike@twisted.space
License: UNKNOWN
Keywords: recollect recollection memento mementos
Platform: UNKNOWN
Classifier: Programming Language :: Python
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Description-Content-Type: text/markdown

# Recollection Overview
Recollection is a state recalling system. It allows for state of objects
to be snap-shotted exposing functionality to then 'rollback' to any
previously stored state.

Recollection gives two distinctly different exposures of this mechanism. If 
performance is not the most critical concern and your focus is on code
simplicity then the inheritence approach is possibly your best option
providing you have ownership of the classes which need historical state.

__Note: This is currently pre-release__

## Installation
You can install this using pip:
```commandline
pip install recollection
```

Alternatively you can get the source from:
https://github.com/mikemalinowski/recollection

## Recollection Inheritence (Inference)

This example shows how to setup a class using inheritence to 
automatically handle state storing. As you can see from the example, there
is no need to explicitly ask recollection to store at any time as it is handled
entirely for you :

```python
import recollection

# -- Inference is the recollection class designed specifically
# -- for inheritence situations.
class Foo(recollection.Inference):

    def __init__(self):
        super(Foo, self).__init__()

        # -- Demonstrate a private attribute with getter
        # -- and setter methods
        self._letter = 1

        # -- Demonstrate public attributes which should be
        # -- stored whenever they are changed
        self.number = 10

        # -- Because we're inheriting from the Inference class, any
        # -- registered properties will automatically trigger a
        # -- state store whenever they are changed.
        self.recollection.register('number')

    # -- Declare that this is a recollection getter
    @recollection.infer.get('foobar')
    def letter(self):
        return self._letter

    # -- Declare this as a recollection setter, using the same
    # -- label as the getter. Whenever setHeight is called
    # -- it will automatically store the state change.
    @recollection.infer.store('foobar')
    def setLetter(self, value):
        self._letter = value

# -- Instance our object
foo = Foo()

# -- Update our variable using our accessor - which will automatically
# -- incur a state store
for letter in ['a', 'b', 'c', 'd', 'e']:
    foo.setLetter(letter)

# -- Demonstrate that the current state is 'e'
print(foo.letter())  # -- Prints 'e'

# -- Roll back one setp and demonstrate that the property
# -- now evaluates to 'd'
foo.recollection.restore(1)
print(foo.letter())  # -- Prints 'd'

# -- Here we demonstrate directly changing properties
# -- which are registered
foo.number = 5
foo.number = 99
print(foo.number == 99)

# -- Now restore back one step
foo.recollection.restore(1)
print(foo.number == 5)
```

## Memento Stack
However, we do not always have the luxury of changing class inheritance
or you may specifically want to keep the recollection state management out
of your actual inheritance hierarchy. The following examples all demonstrate
how this can be achieved.

In this example we have a class with two properties. We the instance
a Memento class targeting our foo instance. Each time we call the
store method within Memento we are taking a snapshot of the values
returned by the registered properties/functions

```python
import recollection


class Foo(object):
    def __init__(self):
        self.name = 'bar'
        self.i = 0

# -- Instance our object
foo = Foo()

# -- Instance a memento object pointing at foo
stack = recollection.Memento(foo)
stack.register('i')

# -- Start changing some values on foo, and
# -- ask our stack to store those changes
for i in range(11):
    foo.i = i

    # -- Ask the memento object to store the state
    stack.store()

# -- Printing i, shows us 10
print(foo.i)

# -- But lets say we roll back to the state 5 versions
# -- ago
stack.restore(5)

# -- Now we can see i is at the version it was when
# -- it was stored 5 versions back
print(foo.i)
```
### Lock-Stepped Storage

It also allows multiple Memento objects to be put into a lock-step,
such that whenever one memento object is storing or restoring then
all other memento objects in that sync group will also store or
restore.

```python
import recollection


class Foo(object):
    def __init__(self):
        self.name = 'bar'
        self.i = 0

# -- This time we instance two completely seperate
# -- foo objects
foo_a = Foo()
foo_b = Foo()

# -- Instance a memento stack for each
stack_a = recollection.Memento(foo_a)
stack_b = recollection.Memento(foo_b)

stack_a.register(['name', 'i'])
stack_b.register(['name', 'i'])

# -- Now we will put our stacks into a state of lock-step
# -- which means whenever one of them is stored or restored
# -- all others in the lock-step group will have the same
# -- action performed
stack_a.group(stack_b)

# -- Increment some values on both objects
for i in range(11):
    foo_a.i = i
    foo_b.i = i

    # -- Trigger a store on only one stack
    stack_a.store()

# -- We can see that both A and B have a value of 10
print(foo_a.i == 10 and foo_b.i == 10)

# -- Now we rollback - knowing that this action will occur
# -- across all grouped memento objects
stack_a.restore(5)

# -- Now we can see i is at the version it was when
# -- it was stored 5 versions back
print(foo_b.i)
print(foo_a.i == 5 and foo_b.i == 5)
```

### Serialisation
Serialisers can also be registered against memento instances allowing
the stored state of a memento object to be serialised into a persistent
state.

This example shows how we might define a user preferences class, and
within that class we define a memento object to store the preference
state. By registering a serialiser the preferences state will be written
to disk whenever the 'store' is called.

Notice that in this example we're also choosing not to store private
member variables, but instead we're harnessing the public api of the
class as getters and setters.

```python
class UserPreferences(object):

    def __init__(self):

        # -- Visual preferences
        self._theme = 'default'

        # -- Define our memento, which we utilise specifically to
        # -- store our preferences to a persistent location
        self._stack = recollection.Memento(self)

        # -- We will utilise the JSON Appdata serialiser, which
        # -- writes our memento information to the app data system
        # -- location
        self._stack.register_serialiser(
            serialiser=recollection.JsonAppSerialiser,
            identifier='memento/demos/userprefs/UserPreferenceA',
        )

        # -- Register which properties we want the store to focus on
        self._stack.register(
            label='theme',
            getter=self.get_theme,
            setter=self.set_theme,
        )

        # -- Finally, we deserialise - which will update this class
        # -- with any previously stored state
        self._stack.deserialise()

    # --------------------------------------------------------------
    def get_theme(self):
        return self._theme

    # --------------------------------------------------------------
    def set_theme(self, theme):
        self._theme = theme
        self._stack.store(serialise=True)
```

### Decoration
Equally, if we want to make it a little more obvious at the class level 
which functions are storing we could opt to utilise the Memento decorator,
which stores and serialises:

```python
from memento import Memento

class UserPreferences(object):

    @recollection.serialise_after('theme')
    def set_theme(self, theme):
        self._theme = theme
```

### Rollback/History
Here we see an example of state being used as a roll-back feature, which
is particularly useful when allowing users to interact with data:

```python
import recollection


class Foo(object):

    def __init__(self):
        self._x = 10
        self._y = 20

        self._stack = recollection.Memento(self)
        self._stack.register(['_x', '_y',])

        # -- Store the default state
        self._stack.store()

    def x(self):
        return self._x

    def setX(self, value):
        self._x = value
        self._stack.store()

    def y(self):
        return self._x

    def setY(self, value):
        self._y = value
        self._stack.store()

    def undo(self):
        self._stack.restore()

# -- Instance our foo object and print the default
# -- value
foo = Foo()
print('Default Value : %s' % foo.x())
        Default Value : 10

# -- Set the value to 200
foo.setX(200)
print('Changed Value : %s' % foo.x())
        Changed Value : 200

# -- Undo the last action, and print that we're not back
# -- to a value the same as the default
foo.undo()
print('Back to the default value after undo : %s' % foo.x())
        Back to the default value after undo : 10
```

# Examples
These mechanics are demonstrated in the example modules, specifically:

### User Preferences Object
```python
# -- This demo shows a user preferences object being interacted
# -- which which works in the same way as the example above.
from recollection.examples.userprefs.demo import demo

demo()
```

### Alternate User Preferences Object

```python
# -- This demo shows a user preferences object being interacted
# -- which which works by decorating setter properties
from recollection.examples.userprefs.demo import demo2

demo2()
```

### Board Game with roll-back
```python
# -- This demo utilises a 'boardgame' style scenario where
# -- we're given two players and the desire to 'undo' the results
# -- of turns if they are not desirable!
from recollection.examples.boardgame.game import demo

demo()
```

### Pin Movement (Multi-attribute altering)
```python
# -- This demo utilises a 'boardgame' style scenario where
# -- we're given two players and the desire to 'undo' the results
# -- of turns if they are not desirable!
from recollection.examples.pins.demo import demo

demo()
```


## Testing and Stability

There are currently unittests which cover most of Memento's core, but it is not yet exhaustive.

## Compatability

This has been tested under Python 2.7.13 and Python 3.6.6 on both Ubuntu and Windows.

## Contribute

If you would like to contribute thoughts, ideas, fixes or features please get in touch! mike@twisted.space


