{ "info": { "author": "Quantlane", "author_email": "code@quantlane.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3 :: Only" ], "description": "Difftrack\n=========\n\n``difftrack`` is a tool for keeping track of changes in data structures.\nIt makes it possible for multiple \"listeners\" to see\nchanges in a dict, a list or any other data structure you want to\nobserve and support (these structures are called \"dispatchers\").\n\n``difftrack`` has two main classes:\n\n- ``Dispatcher`` - acts like a data structure you write to but also sends\n all changes (diffs) to all its listeners.\n- ``Listener`` - a listener is connected to one dispatcher and applies incomming\n diffs to its internal structure so each listener looks like the original data\n structure after applying all those diffs.\n\nThis division allows ``difftrack`` to have multiple listeners in\ndifferent stages of applying diffs, and it enables listeners\nwith special abbilities (e.g. ``difftrack.utils.BoundedListDiffHandler``\nimplementing a \"top N\" list: the list never exceeds a certain fixed size\nbut when some items are deleted, previously invisible elements appear).\n\nBasic usage\n-----------\n\nIn the following example we are going to create a list dispatcher (you can\nwrite to it as to a list using ``__setitem__``, ``__delitem__``\nand ``insert``) and two listeners that will listen for diffs and keep\ntheir own internal state.\n\n.. code:: python\n\n\t>>> import difftrack\n\t>>> dispatcher = difftrack.ListDispatcher()\n\t>>> listener1 = difftrack.ListListener()\n\t>>> listener2 = difftrack.ListListener()\n\t>>> dispatcher.add_listener(listener1)\n\t>>> dispatcher.add_listener(listener2) # create listeners and add them to dispatcher\n\n\t>>> dispatcher.insert(0, 'AAA') # insert string 'AAA' to the first position in list\n\t>>> listener1.get_snapshot() # Diffs are not applied until get_new_diffs() is called\n\t[]\n\t>>> listener1.get_new_diffs() # now we get all diffs that have not been processed yet\n\t[(difftrack.ListDiff.INSERT, 0, 'AAA')]\n\t>>> listener1.get_snapshot() # and we see that listener1's snapshot now contains what we expect\n\t['AAA']\n\t>>> listener2.get_snapshot() # second listener still hasn't got anything because we haven't read its diffs\n\t[]\n\n\t>>> dispatcher.insert(0, 'BBB') # insert new string to 'BBB'\n\t>>> listener1.get_new_diffs() # we need to read new diffs to get current state\n\t[(difftrack.ListDiff.INSERT, 0, 'BBB')]\n\t>>> listener1.get_snapshot() # we inserted 'BBB' to first position so 'AAA' was moved to second position\n\t['BBB', 'AAA']\n\n\t>>> del dispatcher[0] # remove the first element from th list (now 'BBB')\n\t>>> listener1.get_new_diffs()\n\t[(difftrack.ListDiff.DELETE, 0, None)]\n\t>>> listener1.get_snapshot() # we deleted 'BBB' so only 'AAA' remains\n\t['AAA']\n\n\t>>> dispatcher[0] = 'CCC' # overwrite the first element\n\t>>> listener1.get_new_diffs()\n\t[(difftrack.ListDiff.REPLACE, 0, 'CCC')]\n\t>>> listener1.get_snapshot()\n\t['CCC'] # the first and only element in list was overwritten\n\n\t>>> listener2.get_new_diffs() # finally get all diffs for listener2\n\t[(, 0, 'AAA'),\n\t (, 0, 'BBB'),\n\t (, 0, None),\n\t (, 0, 'CCC')]\n\t>>> listener2.get_snapshot() # listener2 is now also up to date\n\t['CCC']\n\nSimilarly you can use ``difftrack`` with ``DictDispatcher`` and\n``DictListener``: you write your changes to an instance of\n``DictDispatcher`` and after applying diffs to listeners you can get a\nsnapshot of the current dictionary state.\n\nCallbacks\n---------\n\n``on_change``\n~~~~~~~~~~~~~\n\nWe can also add a callback to a listener so that we are notified when a diff\ncomes:\n\n.. code:: python\n\n\timport difftrack\n\n\t>>> dispatcher = difftrack.ListDispatcher()\n\t>>> def double_inserted_items(dtype, index, value):\n\t\t''' This generates a new diff *while the current one is processed!* '''\n\t\tif dtype is difftrack.ListDiff.INSERT:\n\t\t\tdispatcher[index] = value * 2\n\n\t>>> listener = difftrack.ListListener(on_change = double_inserted_items) # set function as a callback\n\t>>> dispatcher.add_listener(listener)\n\t>>> dispatcher.insert(0, 7) # insert 7 at index 0 and expect that the result will be doubled\n\t>>> listener.get_new_diffs()\n\t[\n\t\t(difftrack.ListDiff.INSERT, 0, 7),\n\t\t(difftrack.ListDiff.REPLACE, 0, 14)\n\t]\n\t>>> listener.get_snapshot()\n\t[14]\n\nIn this example we show the ``on_change`` callback and its ability to\nwork with a dispatcher. Note that we are first using the\n``ListDiff.INSERT`` operation but the callback triggers a\n``ListDiff.REPLACE`` operation. If it would lead to ``ListDiff.INSERT`` again we\nwould end in recursion and after 10 iterations ``difftrack`` would give up and\nraise an exception.\n\n``on_finalize_batch``\n~~~~~~~~~~~~~~~~~~~~~\n\nThe dispatcher may communicate to its listeners that a certain sequence\nof diffs belongs together, i.e. form a *batch*. We do this by using the\ndispatcher as a context manager, wrapping diff operations that belong together.\n\nA listener may provide another callback called ``on_finalize_batch`` that\ngets called every time the dispatcher finishes dispatching a batch\n(the context is exited).\n\n.. code:: python\n\n\t>>> import difftrack\n\t>>> dispatcher = difftrack.DictDispatcher()\n\t>>> def finalize():\n\t\t\tprint('FINALIZED')\n\n\t>>> def on_change(*args):\n\t\t\tprint('CHANGE')\n\n\t>>> listener = difftrack.DictListener(on_change = on_change, on_finalize_batch = finalize)\n\t>>> dispatcher.add_listener(listener)\n\t>>> with dispatcher: # use the dispatcher as a context manager\n\t\t\tdispatcher[0] = 0\n\t\t\tdispatcher[1] = 1\n\t\t\tdispatcher[2] = 2\n\n\tCHANGE\n\tCHANGE\n\tCHANGE\n\tFINALIZED\n\nWe can see that the ``on_change`` callback is called every time but\n``on_finalize_batch`` only when we exit the context.\n\nUtilities\n---------\n\nThere are several utilities that you might find useful.\n\n``data_mapper``\n~~~~~~~~~~~~~~~\n\nData mapper applies a function to every data field:\n\n.. code:: python\n\n\t>>> import difftrack\n\t>>> def mapper(data: str) -> str:\n\t\t\treturn data.lower()\n\t>>> dispatcher = difftrack.ListDispatcher()\n\t>>> listener = difftrack.ListListener()\n\t>>> dispatcher.add_listener(difftrack.data_mapper(mapper)(listener))\n\n\t>>> dispatcher.insert(0, 'AAA')\n\t>>> dispatcher.insert(0, 'BBB')\n\t>>> listener.get_new_diffs()\n\t[\n\t\t(difftrack.ListDiff.INSERT, 0, 'aaa'),\n\t\t(difftrack.ListDiff.INSERT, 0, 'bbb')\n\t]\n\t>>> listener.get_snapshot()\n\t['bbb', 'aaa']\n\n``compact_dict_diffs``\n~~~~~~~~~~~~~~~~~~~~~~\n\nWhen you update a dict item several times or even delete it you\nsometimes don't want to keep all the changes. You can use *compaction*\nto drop changes that cancel or override each other out:\n\n.. code:: python\n\n\t>>> diffs = [\n\t\t(difftrack.DictDiff.SET, 'x', 123),\n\t\t(difftrack.DictDiff.SET, 'y', 456),\n\t\t(difftrack.DictDiff.SET, 'y', 9999),\n\t\t(difftrack.DictDiff.DELETE, 'x', None),\n\t]\n\t>>> difftrack.compact_dict_diffs(diffs)\n\t[\n\t\t(difftrack.DictDiff.SET, 'y', 9999),\n\t\t(difftrack.DictDiff.DELETE, 'x', None),\n\t]\n\n``compact_list_diffs``\n~~~~~~~~~~~~~~~~~~~~~~\n\nThe same kind of compaction is available for lists as well:\n\n.. code:: python\n\n\t>>> diffs = [\n\t\t(difftrack.ListDiff.INSERT, 0, 'aaa'),\n\t\t(difftrack.ListDiff.INSERT, 1, 'bbb'),\n\t\t(difftrack.ListDiff.DELETE, 0, None)\n\t\t(difftrack.ListDiff.REPLACE, 1, 'ccc'),\n\t]\n\t>>> difftrack.compact_list_diffs(diffs)\n\t[\n\t\t(difftrack.ListDiff.INSERT, 1, 'ccc'),\n\t]\n\n``BoundedListDiffHandler``\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIf we want to keep our list bounded (capped to a certain size) we can use\n``difftrack.BoundedListDiffHandler``.\n\n.. code:: python\n\n\t>>> import difftrack\n\t>>> listener = difftrack.ListListener()\n\t>>> dispatcher = difftrack.ListDispatcher()\n\t>>> dispatcher.add_listener(difftrack.BoundedListDiffHandler(listener, 2)) # bound listener to 2 elements\n\n\t>>> dispatcher.insert(0, 'a')\n\t>>> dispatcher.insert(1, 'b')\n\t>>> dispatcher.insert(2, 'c')\n\t>>> dispatcher.insert(3, 'd')\n\t>>> listener.get_new_diffs()\n\t[\n\t\t(difftrack.ListDiff.INSERT, 0, 'a'),\n\t\t(difftrack.ListDiff.INSERT, 1, 'b'),\n\t]\n\t>>> listener.get_snapshot()\n\t['a', 'b']\n\n\t>>> del dispatcher[0]\n\t>>> listener.get_new_diffs() # 'a' is deleted and 'c' moves to the empty index 1\n\t[\n\t\t(, 0, None),\n\t\t(, 1, 'c')\n\t]\n\t>>> listener.get_snapshot()\n\t['b', 'c']\n\n``squash_list_diffs``\n~~~~~~~~~~~~~~~~~~~~~\n\nThis function groups list diffs affecting consecutive indices.\n\n.. code:: python\n\n\t>>> import difftrack\n\t>>> diffs = [\n\t\t(difftrack.ListDiff.INSERT, 1, 'A'),\n\t\t(difftrack.ListDiff.INSERT, 2, 'B'),\n\t\t(difftrack.ListDiff.INSERT, 3, 'C'),\n\t\t(difftrack.ListDiff.REPLACE, 1, 'D'),\n\t\t(difftrack.ListDiff.DELETE, 1, [])\n\t]\n\t>>> list(difftrack.squash_list_diffs(diffs))\n\t[\n\t\tSquashResults(operation=, start=1, stop=1, payload=['A', 'B', 'C']),\n\t\tSquashResults(operation=, start=1, stop=2, payload=['D']),\n\t\tSquashResults(operation=, start=1, stop=2, payload=[])\n\t]\n\nYou can see that the three consecutive inserts are squashed into a single message. Note that the result\nis no longer a difftrack diff.", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/qntln/difftrack", "keywords": "", "license": "Apache 2.0", "maintainer": "", "maintainer_email": "", "name": "difftrack", "package_url": "https://pypi.org/project/difftrack/", "platform": "", "project_url": "https://pypi.org/project/difftrack/", "project_urls": { "Homepage": "https://github.com/qntln/difftrack" }, "release_url": "https://pypi.org/project/difftrack/0.8.2/", "requires_dist": null, "requires_python": "", "summary": "Keep track of changes in data structures.", "version": "0.8.2" }, "last_serial": 3834713, "releases": { "0.6.0": [ { "comment_text": "", "digests": { "md5": "9f8b5fc8cca79bfc9642578869420bd1", "sha256": "84126aea77612bee67e18e26784cc04799a6b4d5aa4f23540a8d785c17ea229e" }, "downloads": -1, "filename": "difftrack-0.6.0.tar.gz", "has_sig": true, "md5_digest": "9f8b5fc8cca79bfc9642578869420bd1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7347, "upload_time": "2017-07-11T09:37:28", "url": "https://files.pythonhosted.org/packages/a7/9c/4aef06bb0005f97f33f8d65d9247791e30dba06cf58601430cbf8614d849/difftrack-0.6.0.tar.gz" } ], "0.7.0": [ { "comment_text": "", "digests": { "md5": "919f52f07e0b6726dd80c09ff71441e6", "sha256": "27b66a0bf0acfad94ec4af5f29ba4ea75b7d6951a9686e85456a09a33c7f8f2b" }, "downloads": -1, "filename": "difftrack-0.7.0.tar.gz", "has_sig": true, "md5_digest": "919f52f07e0b6726dd80c09ff71441e6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8765, "upload_time": "2017-07-18T12:13:59", "url": "https://files.pythonhosted.org/packages/cf/e6/dbd53b344ddf4cf9084463513d8a023817ab9fa4db991b1ade369a889cd8/difftrack-0.7.0.tar.gz" } ], "0.7.1": [ { "comment_text": "", "digests": { "md5": "7bf13b6416860ebfc9a2599ebd132448", "sha256": "681c3f472be0f9f30f7a6fd2fc41266a3ca0e024260cbe322440d522ae6acbdf" }, "downloads": -1, "filename": "difftrack-0.7.1.tar.gz", "has_sig": true, "md5_digest": "7bf13b6416860ebfc9a2599ebd132448", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8789, "upload_time": "2017-07-18T14:57:17", "url": "https://files.pythonhosted.org/packages/d8/21/d6c2749605b4573bc3502c51962e84b28b65ac7b06812939950d924f8384/difftrack-0.7.1.tar.gz" } ], "0.8.2": [ { "comment_text": "", "digests": { "md5": "1ad20a133cece45a84a1e65241f0b838", "sha256": "2f3b8adce304f8580d60f8510e97a59cb188c9969596cf986a5f2ce688fc6462" }, "downloads": -1, "filename": "difftrack-0.8.2.tar.gz", "has_sig": true, "md5_digest": "1ad20a133cece45a84a1e65241f0b838", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11199, "upload_time": "2018-05-04T15:06:19", "url": "https://files.pythonhosted.org/packages/9d/ab/f37be46d63805725b45349898383b5c839feb0cb1bfcd1bd19802a93a1b5/difftrack-0.8.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "1ad20a133cece45a84a1e65241f0b838", "sha256": "2f3b8adce304f8580d60f8510e97a59cb188c9969596cf986a5f2ce688fc6462" }, "downloads": -1, "filename": "difftrack-0.8.2.tar.gz", "has_sig": true, "md5_digest": "1ad20a133cece45a84a1e65241f0b838", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11199, "upload_time": "2018-05-04T15:06:19", "url": "https://files.pythonhosted.org/packages/9d/ab/f37be46d63805725b45349898383b5c839feb0cb1bfcd1bd19802a93a1b5/difftrack-0.8.2.tar.gz" } ] }