{ "info": { "author": "Mike Malinowski", "author_email": "mike@twisted.space", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python" ], "description": "# Recollection Overview\nRecollection is a state recalling system. It allows for state of objects\nto be snap-shotted exposing functionality to then 'rollback' to any\npreviously stored state.\n\nRecollection gives two distinctly different exposures of this mechanism. If \nperformance is not the most critical concern and your focus is on code\nsimplicity then the inheritence approach is possibly your best option\nproviding you have ownership of the classes which need historical state.\n\n__Note: This is currently pre-release__\n\n## Installation\nYou can install this using pip:\n```commandline\npip install recollection\n```\n\nAlternatively you can get the source from:\nhttps://github.com/mikemalinowski/recollection\n\n## Recollection Inheritence (Inference)\n\nThis example shows how to setup a class using inheritence to \nautomatically handle state storing. As you can see from the example, there\nis no need to explicitly ask recollection to store at any time as it is handled\nentirely for you. This example specifically shows attribute storing :\n\n```python\nimport recollection\n\n# -- Inference is the recollection class designed specifically\n# -- for inheritance situations.\nclass Foo(recollection.Inference):\n\n def __init__(self):\n super(Foo, self).__init__()\n\n self.number = 10\n\n # -- Register 'number' as a property to monitor\n self.memento.register('number')\n\n\n# -- Instance our object\nfoo = Foo()\n\n# -- Here we demonstrate directly changing properties\n# -- which are registered\nfoo.number = 5\nfoo.number = 99\nprint(foo.number == 99)\n\n# -- Now restore back one step\nfoo.recollection.restore(1)\nprint(foo.number == 5)\n```\n\nWhilst this example shows how decorators can be utilised to store method\ngetters and setters:\n\n```python\nimport recollection\n\n# -- Inference is the recollection class designed specifically\n# -- for inheritence situations.\nclass Foo(recollection.Inference):\n\n def __init__(self):\n super(Foo, self).__init__()\n self._number = 10\n\n # -- Declare that this is a recollection getter\n @recollection.infer.get('number')\n def number(self):\n return self._number\n\n # -- Declare this as a recollection setter\n @recollection.infer.store('number')\n def set_number(self, value):\n self._number = value\n\n# -- Instance our object\nfoo = Foo()\n\n# -- Update our variable using our accessor \nfor number in range(10):\n foo.set_number(number)\n\n# -- Demonstrate that the current state is 'e'\nprint(foo.number()) # -- Prints 9\n\n# -- Roll back one step in the memento history\nfoo.recollection.restore(1)\nprint(foo.number()) # -- Prints 8\n\n```\n\n## Memento Stack\nHowever, we do not always have the luxury of changing class inheritance\nor you may specifically want to keep the recollection state management out\nof your actual inheritance hierarchy. The following examples all demonstrate\nhow this can be achieved.\n\nIn this example we have a class with two properties. We the instance\na Memento class targeting our foo instance. Each time we call the\nstore method within Memento we are taking a snapshot of the values\nreturned by the registered properties/functions\n\n```python\nimport recollection\n\n\nclass Foo(object):\n def __init__(self):\n self.number = 0\n\n# -- Instance our object\nfoo = Foo()\n\n# -- Instance a memento object pointing at foo\nmemento = recollection.Memento(foo)\nmemento.register('number')\n\n# -- Start changing some values on foo, and\n# -- ask our stack to store those changes\nfor number in range(11):\n foo.number = number\n\n # -- Ask the memento object to store the state\n memento.store()\n\n# -- Printing i, shows us 10\nprint(foo.number)\n\n# -- But lets say we roll back to the state 5 versions\n# -- ago\nmemento.restore(5)\n\n# -- Now we can see i is at the version it was when\n# -- it was stored 5 versions back\nprint(foo.number)\n```\n### Lock-Stepped Storage\n\nIt also allows multiple Memento objects to be put into a lock-step,\nsuch that whenever one memento object is storing or restoring then\nall other memento objects in that sync group will also store or\nrestore.\n\n```python\nimport recollection\n\n\nclass Foo(object):\n def __init__(self):\n self.number = 0\n\n# -- This time we instance two completely seperate\n# -- foo objects\nfoo_a = Foo()\nfoo_b = Foo()\n\n# -- Instance a memento stack for each\nmemento_a = recollection.Memento(foo_a)\nmemento_b = recollection.Memento(foo_b)\n\nmemento_a.register('number')\nmemento_b.register('number')\n\n# -- Now we will put our stacks into a state of lock-step\n# -- which means whenever one of them is stored or restored\n# -- all others in the lock-step group will have the same\n# -- action performed\nmemento_a.group(memento_b)\n\n# -- Increment some values on both objects\nfor i in range(11):\n foo_a.i = i\n foo_b.i = i\n\n # -- Trigger a store on only one stack\n memento_a.store()\n\n# -- We can see that both A and B have a value of 10\nprint(foo_a.i == 10 and foo_b.i == 10)\n\n# -- Now we rollback - knowing that this action will occur\n# -- across all grouped memento objects\nmemento_a.restore(5)\n\n# -- Now we can see i is at the version it was when\n# -- it was stored 5 versions back\nprint(foo_a.i == 5 and foo_b.i == 5)\n```\n\n### Serialisation\nSerialisers can also be registered against memento instances allowing\nthe stored state of a memento object to be serialised into a persistent\nstate.\n\nThis example shows how we might define a user preferences class, and\nwithin that class we define a memento object to store the preference\nstate. By registering a serialiser the preferences state will be written\nto disk whenever the 'store' is called.\n\nNotice that in this example we're also choosing not to store private\nmember variables, but instead we're harnessing the public api of the\nclass as getters and setters.\n\n```python\nclass UserPreferences(object):\n\n def __init__(self):\n\n # -- Visual preferences\n self._theme = 'default'\n\n # -- Define our memento, which we utilise specifically to\n # -- store our preferences to a persistent location\n self._memento = recollection.Memento(self)\n\n # -- We will utilise the JSON Appdata serialiser, which\n # -- writes our memento information to the app data system\n # -- location\n self._memento.register_serialiser(\n serialiser=recollection.JsonAppSerialiser,\n identifier='memento/demos/userprefs/UserPreferenceA',\n )\n\n # -- Register which properties we want the store to focus on\n self._memento.register(\n label='theme',\n getter=self.get_theme,\n setter=self.set_theme,\n )\n\n # -- Finally, we deserialise - which will update this class\n # -- with any previously stored state\n self._memento.deserialise()\n\n # --------------------------------------------------------------\n def get_theme(self):\n return self._theme\n\n # --------------------------------------------------------------\n def set_theme(self, theme):\n self._theme = theme\n self._memento.store(serialise=True)\n```\n\n### Decoration\nEqually, if we want to make it a little more obvious at the class level \nwhich functions are storing we could opt to utilise the Memento decorator,\nwhich stores and serialises:\n\n```python\nclass UserPreferences(object):\n\n @recollection.serialise_after('theme')\n def set_theme(self, theme):\n self._theme = theme\n```\n\n# Examples\nThese mechanics are all demonstrated in the example modules, specifically:\n\n### User Preferences Object\nThis demo shows a user preferences object being interacted\nwhich which works in the same way as the example above, where the\nsettings are stored as changed come in - allowing the preferences to\nbe 'undone'.\n```python\n# -- This demo shows a user preferences object being interacted\n# -- which which works in the same way as the example above.\nfrom recollection.examples.userprefs.demo import demo\n\ndemo()\n```\n\n### Alternate User Preferences Object\nThis demo is identical to the demo above in terms of output but is \nhandled through decorators.\n```python\nfrom recollection.examples.userprefs.demo import demo2\n\ndemo2()\n```\n\n### Board Game with roll-back\nThis demo utilises a 'boardgame' style scenario where\nwe're given two players and the desire to 'undo' the results\nof turns if they are not desirable!\n```python\nfrom recollection.examples.boardgame.game import demo\n\ndemo()\n```\n\n### Pin Movement (Multi-attribute altering)\nThis demo shows the utilisation of a setter which is actually setting\nmultiple recorded attributes but wants to have a single restore step.\n```python\nfrom recollection.examples.pins.demo import demo\n\ndemo()\n```\n\n### Renamer (Ui and Code)\nThis demo shows how recollection can be used to store state of a functional\nobject which is represented as a visual tool (utilising PySide2).\n```python\nfrom recollection.examples.renamer.demo import demo\n\ndemo()\n```\n\n## Testing and Stability\n\nThere are currently unittests which cover most of Memento's core, but it is not yet exhaustive.\n\n## Compatability\n\nThis has been tested under Python 2.7.13 and Python 3.6.6 on both Ubuntu and Windows.\n\n## Contribute\n\nIf you would like to contribute thoughts, ideas, fixes or features please get in touch! mike@twisted.space\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/mikemalinowski/recollection", "keywords": "recollect recollection memento mementos", "license": "", "maintainer": "", "maintainer_email": "", "name": "recollection", "package_url": "https://pypi.org/project/recollection/", "platform": "", "project_url": "https://pypi.org/project/recollection/", "project_urls": { "Homepage": "https://github.com/mikemalinowski/recollection" }, "release_url": "https://pypi.org/project/recollection/0.9.3/", "requires_dist": [ "six" ], "requires_python": "", "summary": "A python package exposing the memento design pattern", "version": "0.9.3" }, "last_serial": 4524866, "releases": { "0.9.0": [ { "comment_text": "", "digests": { "md5": "f3d67682f9b41dd33b5888d6e00d3d42", "sha256": "86ee65e9c896e0acf412e8b19bf5aac1c056b81aaec8a19397e2ec9b2d2db630" }, "downloads": -1, "filename": "recollection-0.9.0-py3-none-any.whl", "has_sig": false, "md5_digest": "f3d67682f9b41dd33b5888d6e00d3d42", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 36735, "upload_time": "2018-11-22T21:23:39", "url": "https://files.pythonhosted.org/packages/3b/29/ce28851f73e3a7bcb1ddd6b7632fc1f4b3b65e4e0a6d79c497b9cd65377e/recollection-0.9.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c2c3d05e78296c521e124cc3616a26a0", "sha256": "c609b3c0f7cad5305cefeafa71fd266c5524f320bb89b2a04299db4b74122aff" }, "downloads": -1, "filename": "recollection-0.9.0.tar.gz", "has_sig": false, "md5_digest": "c2c3d05e78296c521e124cc3616a26a0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29322, "upload_time": "2018-11-22T21:23:41", "url": "https://files.pythonhosted.org/packages/92/ff/22e6e18348373b0e816e08621a4d2910a3574cedb7efba1474e355b2306c/recollection-0.9.0.tar.gz" } ], "0.9.1": [ { "comment_text": "", "digests": { "md5": "63c3fd331e5e323402def20c5f33ec5b", "sha256": "3c655a15b15058ffcb393fd92fd52b8cb5002d9316662f23593cbd27806edc6e" }, "downloads": -1, "filename": "recollection-0.9.1-py3-none-any.whl", "has_sig": false, "md5_digest": "63c3fd331e5e323402def20c5f33ec5b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 36739, "upload_time": "2018-11-22T21:29:28", "url": "https://files.pythonhosted.org/packages/37/17/06ff8682e611b879acc62d583438bd22cd3190fff509bef7e3e43d9eb28e/recollection-0.9.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "e2ff0b733cbb10291687c438aa501f61", "sha256": "707c13305c2dc163779ca4b402902c3b47982d4acd71d497f2fe6b46210575c8" }, "downloads": -1, "filename": "recollection-0.9.1.tar.gz", "has_sig": false, "md5_digest": "e2ff0b733cbb10291687c438aa501f61", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29323, "upload_time": "2018-11-22T21:29:29", "url": "https://files.pythonhosted.org/packages/ae/78/5513ba68b5778893b4af310aeec9fa086e09d278469e98fc950fe2d404e6/recollection-0.9.1.tar.gz" } ], "0.9.2": [ { "comment_text": "", "digests": { "md5": "cffc9a3352519773e557ec3fc194458f", "sha256": "69eb94dddef67abc840ce456c0610040e67d3528fe2b8e3064f6477527afa92a" }, "downloads": -1, "filename": "recollection-0.9.2-py3-none-any.whl", "has_sig": false, "md5_digest": "cffc9a3352519773e557ec3fc194458f", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 36398, "upload_time": "2018-11-23T18:51:06", "url": "https://files.pythonhosted.org/packages/80/ab/1f76061599e7ececa6680c9012af3ff18268610ac63da92be4dd6ba8d07b/recollection-0.9.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2120d9f4fdf2b648b5e7b31162682c83", "sha256": "f0769676e95d858e2b2609575e211b745d84faa211a2bd34d212b3379824e5b2" }, "downloads": -1, "filename": "recollection-0.9.2.tar.gz", "has_sig": false, "md5_digest": "2120d9f4fdf2b648b5e7b31162682c83", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 28921, "upload_time": "2018-11-23T18:51:07", "url": "https://files.pythonhosted.org/packages/2e/6d/3fabe0fee0c54532d471aee8aec351e91930b13a30a349215592ae1a1a25/recollection-0.9.2.tar.gz" } ], "0.9.3": [ { "comment_text": "", "digests": { "md5": "d686f0767f29d06a9889d4fe7b56d915", "sha256": "ec6292da1cd67f8dbc4627258143a465fc7b69a136ee15d8505ac357354c93ff" }, "downloads": -1, "filename": "recollection-0.9.3-py3-none-any.whl", "has_sig": false, "md5_digest": "d686f0767f29d06a9889d4fe7b56d915", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 40641, "upload_time": "2018-11-25T00:24:18", "url": "https://files.pythonhosted.org/packages/ea/d4/75eaf1193396289ad207d397a755a206ad4c0366c3ecc6b8f6758bea2b82/recollection-0.9.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "bce680ca5e27702607cf98459910bb88", "sha256": "cc314fdd17bca371fd5912f7024a2c7bc7a9dba76b02652cd5224e9c731f0baf" }, "downloads": -1, "filename": "recollection-0.9.3.tar.gz", "has_sig": false, "md5_digest": "bce680ca5e27702607cf98459910bb88", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32633, "upload_time": "2018-11-25T00:24:19", "url": "https://files.pythonhosted.org/packages/56/60/6c6309701ebb4422352273bc53e60fa1ca9985a74141a30e3365b9bebcdb/recollection-0.9.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "d686f0767f29d06a9889d4fe7b56d915", "sha256": "ec6292da1cd67f8dbc4627258143a465fc7b69a136ee15d8505ac357354c93ff" }, "downloads": -1, "filename": "recollection-0.9.3-py3-none-any.whl", "has_sig": false, "md5_digest": "d686f0767f29d06a9889d4fe7b56d915", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 40641, "upload_time": "2018-11-25T00:24:18", "url": "https://files.pythonhosted.org/packages/ea/d4/75eaf1193396289ad207d397a755a206ad4c0366c3ecc6b8f6758bea2b82/recollection-0.9.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "bce680ca5e27702607cf98459910bb88", "sha256": "cc314fdd17bca371fd5912f7024a2c7bc7a9dba76b02652cd5224e9c731f0baf" }, "downloads": -1, "filename": "recollection-0.9.3.tar.gz", "has_sig": false, "md5_digest": "bce680ca5e27702607cf98459910bb88", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32633, "upload_time": "2018-11-25T00:24:19", "url": "https://files.pythonhosted.org/packages/56/60/6c6309701ebb4422352273bc53e60fa1ca9985a74141a30e3365b9bebcdb/recollection-0.9.3.tar.gz" } ] }