{"action":"created","comment":{"url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/comments/68747482","id":68747482,"diff_hunk":"@@ -95,6 +100,152 @@ def apply(self, obj):\n         return obj.transform(self.path, lambda x: x.add(self.item))\n \n \n+_sentinel = object()\n+\n+\n+class _EvolverProxy(object):\n+    \"\"\"\n+    This attempts to bunch all the diff operations for a particular object into\n+    a single transaction so that related attributes can be ``set`` without\n+    triggering an in invariant error.\n+    Additionally, the leaf nodes are persisted first and in isolation, so as\n+    not to trigger invariant errors in ancestor nodes.\n+    \"\"\"\n+    def __init__(self, original):\n+        \"\"\"\n+        :param PClass original: The root object to which transformations will\n+            be applied.\n+        \"\"\"\n+        self._original = original\n+        self._evolver = original.evolver()\n+        self._children = {}\n+        self._operations = []\n+\n+    def _child(self, segment):\n+        child = self._children.get(segment)\n+        if child is not None:\n+            return child\n+        child = _get(self._original, segment, _sentinel)\n+        if child is _sentinel:\n+            raise KeyError(\n+                'Segment not found in path. '\n+                'Parent: {}, '\n+                'Segment: {}'.format(self, segment)\n+            )\n+        proxy_for_child = _EvolverProxy(child)\n+        self._children[segment] = proxy_for_child\n+        return proxy_for_child\n+\n+    def transform(self, path, operation):\n+        \"\"\"\n+        Traverse each segment of ``path`` to create a hierarchy of\n+        ``_EvolverProxy`` objects and perform the ``operation`` on the\n+        resulting leaf proxy object. This will infact perform the operation on\n+        an evolver of the original Pyrsistent object.\n+\n+        :param PVector path: The path relative to ``original`` which will be\n+            operated on.\n+        :param callable operation: A function to be applied to an evolver of\n+             the object at ``path``\n+        :returns: ``self``\n+        \"\"\"\n+        target = self\n+        for segment in path:\n+            target = target._child(segment)\n+        operation(target)\n+        return self\n+\n+    def add(self, item):\n+        \"\"\"\n+        Add ``item`` to the ``original`` ``Pset`` or if the item is itself a\n+        Pyrsistent object, add a new proxy for that item so that further\n+        operations can be performed on it without triggering invariant checks\n+        until the tree is finally committed.\n+\n+        :param item: An object to be added to the ``PSet`` wrapped by this\n+            proxy.\n+        :returns: ``self``\n+        \"\"\"\n+        if hasattr(item, 'evolver'):","path":"flocker/control/_diffing.py","position":98,"original_position":98,"commit_id":"5352c86984b8e6715960e6a54142d2577a4e9a0b","original_commit_id":"5352c86984b8e6715960e6a54142d2577a4e9a0b","user":{"login":"exarkun","id":254565,"avatar_url":"https://avatars.githubusercontent.com/u/254565?v=3","gravatar_id":"","url":"https://api.github.com/users/exarkun","html_url":"https://github.com/exarkun","followers_url":"https://api.github.com/users/exarkun/followers","following_url":"https://api.github.com/users/exarkun/following{/other_user}","gists_url":"https://api.github.com/users/exarkun/gists{/gist_id}","starred_url":"https://api.github.com/users/exarkun/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/exarkun/subscriptions","organizations_url":"https://api.github.com/users/exarkun/orgs","repos_url":"https://api.github.com/users/exarkun/repos","events_url":"https://api.github.com/users/exarkun/events{/privacy}","received_events_url":"https://api.github.com/users/exarkun/received_events","type":"User","site_admin":false},"body":"Also I can't quite figure out what this is checking for or why.  Is it looking for an evolver method as a way to detect a pyrsistent type that can have an evolver for it created?  That's my only guess.  If it's that, some docs to that effect would help.  Also, I might suggest an alternate way of detecting such things.  Make a zope.interface.Interface for an object that can make an evolver (I guess this just means it has an \"evolver\" method, maybe it returns an object that provides another evolver interface?).  Use https://docs.zope.org/zope.interface/api.html#zope-interface-declarations-classimplements to declare that the pyrsistent types you're trying to support implement that interface, then use zope.interface to check for the interface instead of checking for an evolver attribute.  This makes the interface this interaction depends on explicit and removes the chance of an accidental collision with another object that just happens to have an unrelated \"evolver\" attribute.\r\n\r\nAlternatively, `isinstance(item, (all the pyrsistent types))` instead of hasattr() (or getattr).","created_at":"2016-06-28T12:35:49Z","updated_at":"2016-06-28T12:35:49Z","html_url":"https://github.com/ClusterHQ/flocker/pull/2839#discussion_r68747482","pull_request_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/2839","_links":{"self":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/comments/68747482"},"html":{"href":"https://github.com/ClusterHQ/flocker/pull/2839#discussion_r68747482"},"pull_request":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/2839"}}},"pull_request":{"url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/2839","id":74636885,"html_url":"https://github.com/ClusterHQ/flocker/pull/2839","diff_url":"https://github.com/ClusterHQ/flocker/pull/2839.diff","patch_url":"https://github.com/ClusterHQ/flocker/pull/2839.patch","issue_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/2839","number":2839,"state":"open","locked":false,"title":"[FLOC-4452] Delay invariant checks until all diff operations have been applied","user":{"login":"wallrj","id":978965,"avatar_url":"https://avatars.githubusercontent.com/u/978965?v=3","gravatar_id":"","url":"https://api.github.com/users/wallrj","html_url":"https://github.com/wallrj","followers_url":"https://api.github.com/users/wallrj/followers","following_url":"https://api.github.com/users/wallrj/following{/other_user}","gists_url":"https://api.github.com/users/wallrj/gists{/gist_id}","starred_url":"https://api.github.com/users/wallrj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wallrj/subscriptions","organizations_url":"https://api.github.com/users/wallrj/orgs","repos_url":"https://api.github.com/users/wallrj/repos","events_url":"https://api.github.com/users/wallrj/events{/privacy}","received_events_url":"https://api.github.com/users/wallrj/received_events","type":"User","site_admin":false},"body":"Fixes: https://clusterhq.atlassian.net/browse/FLOC-4452","created_at":"2016-06-21T15:48:30Z","updated_at":"2016-06-28T12:35:49Z","closed_at":null,"merged_at":null,"merge_commit_sha":"db5fbf5bd010109d3c8cb993eef62d357591f3c1","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/2839/commits","review_comments_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/2839/comments","review_comment_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/2839/comments","statuses_url":"https://api.github.com/repos/ClusterHQ/flocker/statuses/5352c86984b8e6715960e6a54142d2577a4e9a0b","head":{"label":"ClusterHQ:invariant-error-FLOC-4452","ref":"invariant-error-FLOC-4452","sha":"5352c86984b8e6715960e6a54142d2577a4e9a0b","user":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"repo":{"id":19248542,"name":"flocker","full_name":"ClusterHQ/flocker","owner":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ClusterHQ/flocker","description":"Container data volume manager for your Dockerized application","fork":false,"url":"https://api.github.com/repos/ClusterHQ/flocker","forks_url":"https://api.github.com/repos/ClusterHQ/flocker/forks","keys_url":"https://api.github.com/repos/ClusterHQ/flocker/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ClusterHQ/flocker/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ClusterHQ/flocker/teams","hooks_url":"https://api.github.com/repos/ClusterHQ/flocker/hooks","issue_events_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/events{/number}","events_url":"https://api.github.com/repos/ClusterHQ/flocker/events","assignees_url":"https://api.github.com/repos/ClusterHQ/flocker/assignees{/user}","branches_url":"https://api.github.com/repos/ClusterHQ/flocker/branches{/branch}","tags_url":"https://api.github.com/repos/ClusterHQ/flocker/tags","blobs_url":"https://api.github.com/repos/ClusterHQ/flocker/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ClusterHQ/flocker/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ClusterHQ/flocker/git/refs{/sha}","trees_url":"https://api.github.com/repos/ClusterHQ/flocker/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ClusterHQ/flocker/statuses/{sha}","languages_url":"https://api.github.com/repos/ClusterHQ/flocker/languages","stargazers_url":"https://api.github.com/repos/ClusterHQ/flocker/stargazers","contributors_url":"https://api.github.com/repos/ClusterHQ/flocker/contributors","subscribers_url":"https://api.github.com/repos/ClusterHQ/flocker/subscribers","subscription_url":"https://api.github.com/repos/ClusterHQ/flocker/subscription","commits_url":"https://api.github.com/repos/ClusterHQ/flocker/commits{/sha}","git_commits_url":"https://api.github.com/repos/ClusterHQ/flocker/git/commits{/sha}","comments_url":"https://api.github.com/repos/ClusterHQ/flocker/comments{/number}","issue_comment_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/comments{/number}","contents_url":"https://api.github.com/repos/ClusterHQ/flocker/contents/{+path}","compare_url":"https://api.github.com/repos/ClusterHQ/flocker/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ClusterHQ/flocker/merges","archive_url":"https://api.github.com/repos/ClusterHQ/flocker/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ClusterHQ/flocker/downloads","issues_url":"https://api.github.com/repos/ClusterHQ/flocker/issues{/number}","pulls_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls{/number}","milestones_url":"https://api.github.com/repos/ClusterHQ/flocker/milestones{/number}","notifications_url":"https://api.github.com/repos/ClusterHQ/flocker/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ClusterHQ/flocker/labels{/name}","releases_url":"https://api.github.com/repos/ClusterHQ/flocker/releases{/id}","deployments_url":"https://api.github.com/repos/ClusterHQ/flocker/deployments","created_at":"2014-04-28T18:02:14Z","updated_at":"2016-06-28T02:54:27Z","pushed_at":"2016-06-28T10:15:54Z","git_url":"git://github.com/ClusterHQ/flocker.git","ssh_url":"git@github.com:ClusterHQ/flocker.git","clone_url":"https://github.com/ClusterHQ/flocker.git","svn_url":"https://github.com/ClusterHQ/flocker","homepage":"https://clusterhq.com","size":60218,"stargazers_count":2367,"watchers_count":2367,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":183,"mirror_url":null,"open_issues_count":50,"forks":183,"open_issues":50,"watchers":2367,"default_branch":"master"}},"base":{"label":"ClusterHQ:master","ref":"master","sha":"3fdfae51e2ca3d4eb2130f681feaa6fb0643747e","user":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"repo":{"id":19248542,"name":"flocker","full_name":"ClusterHQ/flocker","owner":{"login":"ClusterHQ","id":7913449,"avatar_url":"https://avatars.githubusercontent.com/u/7913449?v=3","gravatar_id":"","url":"https://api.github.com/users/ClusterHQ","html_url":"https://github.com/ClusterHQ","followers_url":"https://api.github.com/users/ClusterHQ/followers","following_url":"https://api.github.com/users/ClusterHQ/following{/other_user}","gists_url":"https://api.github.com/users/ClusterHQ/gists{/gist_id}","starred_url":"https://api.github.com/users/ClusterHQ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ClusterHQ/subscriptions","organizations_url":"https://api.github.com/users/ClusterHQ/orgs","repos_url":"https://api.github.com/users/ClusterHQ/repos","events_url":"https://api.github.com/users/ClusterHQ/events{/privacy}","received_events_url":"https://api.github.com/users/ClusterHQ/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ClusterHQ/flocker","description":"Container data volume manager for your Dockerized application","fork":false,"url":"https://api.github.com/repos/ClusterHQ/flocker","forks_url":"https://api.github.com/repos/ClusterHQ/flocker/forks","keys_url":"https://api.github.com/repos/ClusterHQ/flocker/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ClusterHQ/flocker/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ClusterHQ/flocker/teams","hooks_url":"https://api.github.com/repos/ClusterHQ/flocker/hooks","issue_events_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/events{/number}","events_url":"https://api.github.com/repos/ClusterHQ/flocker/events","assignees_url":"https://api.github.com/repos/ClusterHQ/flocker/assignees{/user}","branches_url":"https://api.github.com/repos/ClusterHQ/flocker/branches{/branch}","tags_url":"https://api.github.com/repos/ClusterHQ/flocker/tags","blobs_url":"https://api.github.com/repos/ClusterHQ/flocker/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ClusterHQ/flocker/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ClusterHQ/flocker/git/refs{/sha}","trees_url":"https://api.github.com/repos/ClusterHQ/flocker/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ClusterHQ/flocker/statuses/{sha}","languages_url":"https://api.github.com/repos/ClusterHQ/flocker/languages","stargazers_url":"https://api.github.com/repos/ClusterHQ/flocker/stargazers","contributors_url":"https://api.github.com/repos/ClusterHQ/flocker/contributors","subscribers_url":"https://api.github.com/repos/ClusterHQ/flocker/subscribers","subscription_url":"https://api.github.com/repos/ClusterHQ/flocker/subscription","commits_url":"https://api.github.com/repos/ClusterHQ/flocker/commits{/sha}","git_commits_url":"https://api.github.com/repos/ClusterHQ/flocker/git/commits{/sha}","comments_url":"https://api.github.com/repos/ClusterHQ/flocker/comments{/number}","issue_comment_url":"https://api.github.com/repos/ClusterHQ/flocker/issues/comments{/number}","contents_url":"https://api.github.com/repos/ClusterHQ/flocker/contents/{+path}","compare_url":"https://api.github.com/repos/ClusterHQ/flocker/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ClusterHQ/flocker/merges","archive_url":"https://api.github.com/repos/ClusterHQ/flocker/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ClusterHQ/flocker/downloads","issues_url":"https://api.github.com/repos/ClusterHQ/flocker/issues{/number}","pulls_url":"https://api.github.com/repos/ClusterHQ/flocker/pulls{/number}","milestones_url":"https://api.github.com/repos/ClusterHQ/flocker/milestones{/number}","notifications_url":"https://api.github.com/repos/ClusterHQ/flocker/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ClusterHQ/flocker/labels{/name}","releases_url":"https://api.github.com/repos/ClusterHQ/flocker/releases{/id}","deployments_url":"https://api.github.com/repos/ClusterHQ/flocker/deployments","created_at":"2014-04-28T18:02:14Z","updated_at":"2016-06-28T02:54:27Z","pushed_at":"2016-06-28T10:15:54Z","git_url":"git://github.com/ClusterHQ/flocker.git","ssh_url":"git@github.com:ClusterHQ/flocker.git","clone_url":"https://github.com/ClusterHQ/flocker.git","svn_url":"https://github.com/ClusterHQ/flocker","homepage":"https://clusterhq.com","size":60218,"stargazers_count":2367,"watchers_count":2367,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":183,"mirror_url":null,"open_issues_count":50,"forks":183,"open_issues":50,"watchers":2367,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/2839"},"html":{"href":"https://github.com/ClusterHQ/flocker/pull/2839"},"issue":{"href":"https://api.github.com/repos/ClusterHQ/flocker/issues/2839"},"comments":{"href":"https://api.github.com/repos/ClusterHQ/flocker/issues/2839/comments"},"review_comments":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/2839/comments"},"review_comment":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ClusterHQ/flocker/pulls/2839/commits"},"statuses":{"href":"https://api.github.com/repos/ClusterHQ/flocker/statuses/5352c86984b8e6715960e6a54142d2577a4e9a0b"}}}}
{"actor":{"display_login":"exarkun"}}