{"action":"created","comment":{"url":"https://api.github.com/repos/eventbrite/pysoa/pulls/comments/131769904","pull_request_review_id":54779322,"id":131769904,"diff_hunk":"@@ -54,11 +174,153 @@ def __str__(self):\n             errors_string = '\\n'.join(['{a.action}: {a.errors}'.format(a=a) for a in self.actions])\n             return 'Error calling action(s):\\n{}'.format(errors_string)\n \n-    @staticmethod\n-    def generate_correlation_id():\n-        return six.u(uuid.uuid1().hex)\n+    # Synchronous request methods\n \n-    def make_control_header(\n+    def call_action(\n+        self,\n+        service_name,\n+        action,\n+        body=None,\n+        **kwargs\n+    ):\n+        \"\"\"\n+        Build and send a single job request with one action.\n+\n+        Returns the action response or raises an exception if the action response is an error.\n+\n+        Args:\n+            action: string\n+            body: dict\n+            switches: list of ints\n+            context: dict\n+            correlation_id: string\n+        Returns:\n+            ActionResponse\n+        \"\"\"\n+        action_request = ActionRequest(\n+            action=action,\n+            body=body or {},\n+        )\n+        return self.call_actions(\n+            service_name,\n+            [action_request],\n+            **kwargs\n+        ).actions[0]\n+\n+    def call_actions(self, service_name, actions, expansions=None, **kwargs):\n+        \"\"\"\n+        Build and send a single job request with one or more actions.\n+\n+        Returns a list of action responses, one for each action, or raises an exception if any action response is an\n+        error.\n+\n+        This method performs expansions if the Client is configured with an expansion converter.\n+\n+        Args:\n+            actions: list of ActionRequest or dict\n+            switches: list\n+            context: dict\n+            correlation_id: string\n+            continue_on_error: bool\n+            control_extra: dict\n+        Returns:\n+            JobResponse\n+        \"\"\"\n+        request_id = self.send_request(service_name, actions, **kwargs)\n+        # Dump everything from the generator. There should only be one response.\n+        responses = list(self.get_all_responses(service_name))\n+        response_id, response = responses[0]\n+        if response_id != request_id:\n+            raise Exception('Got response with ID {} for request with ID {}'.format(response_id, request_id))\n+        if response.errors:\n+            raise self.JobError(response.errors)\n+        error_actions = [action for action in response.actions if action.errors]\n+        if error_actions:\n+            raise self.CallActionError(error_actions)\n+\n+        # Perform expansions\n+        if expansions and hasattr(self, 'expansion_converter'):\n+            # Initialize service request cache\n+            exp_service_requests = {service_name: {}}\n+\n+            # Build initial list of objects to expand\n+            objs_to_expand = []\n+            for type_node in self.expansion_converter.dict_to_trees(expansions):\n+                for action in response.actions:\n+                    exp_objects = type_node.find_objects(action.body)\n+                    objs_to_expand.extend(\n+                        (exp_object, type_node.expansions)\n+                        for exp_object in exp_objects\n+                    )\n+\n+            # Loop until we have no outstanding requests or responses\n+            while objs_to_expand or any(requests for requests in exp_service_requests.values()):","path":"pysoa/client/client.py","position":238,"original_position":238,"commit_id":"ffbb6442255c717a1ea41db3486aeb74cd194852","original_commit_id":"ffbb6442255c717a1ea41db3486aeb74cd194852","user":{"login":"sethbrite","id":11317082,"avatar_url":"https://avatars3.githubusercontent.com/u/11317082?v=4","gravatar_id":"","url":"https://api.github.com/users/sethbrite","html_url":"https://github.com/sethbrite","followers_url":"https://api.github.com/users/sethbrite/followers","following_url":"https://api.github.com/users/sethbrite/following{/other_user}","gists_url":"https://api.github.com/users/sethbrite/gists{/gist_id}","starred_url":"https://api.github.com/users/sethbrite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sethbrite/subscriptions","organizations_url":"https://api.github.com/users/sethbrite/orgs","repos_url":"https://api.github.com/users/sethbrite/repos","events_url":"https://api.github.com/users/sethbrite/events{/privacy}","received_events_url":"https://api.github.com/users/sethbrite/received_events","type":"User","site_admin":false},"body":"Just curious, what happened to the `six` here and in other iterations in this file?","created_at":"2017-08-07T21:37:04Z","updated_at":"2017-08-07T21:37:47Z","html_url":"https://github.com/eventbrite/pysoa/pull/8#discussion_r131769904","pull_request_url":"https://api.github.com/repos/eventbrite/pysoa/pulls/8","_links":{"self":{"href":"https://api.github.com/repos/eventbrite/pysoa/pulls/comments/131769904"},"html":{"href":"https://github.com/eventbrite/pysoa/pull/8#discussion_r131769904"},"pull_request":{"href":"https://api.github.com/repos/eventbrite/pysoa/pulls/8"}}},"pull_request":{"url":"https://api.github.com/repos/eventbrite/pysoa/pulls/8","id":133815373,"html_url":"https://github.com/eventbrite/pysoa/pull/8","diff_url":"https://github.com/eventbrite/pysoa/pull/8.diff","patch_url":"https://github.com/eventbrite/pysoa/pull/8.patch","issue_url":"https://api.github.com/repos/eventbrite/pysoa/issues/8","number":8,"state":"open","locked":false,"title":"Refactor Client to include routing functionality, and remove Router","user":{"login":"bgreenberg-eb","id":16325960,"avatar_url":"https://avatars3.githubusercontent.com/u/16325960?v=4","gravatar_id":"","url":"https://api.github.com/users/bgreenberg-eb","html_url":"https://github.com/bgreenberg-eb","followers_url":"https://api.github.com/users/bgreenberg-eb/followers","following_url":"https://api.github.com/users/bgreenberg-eb/following{/other_user}","gists_url":"https://api.github.com/users/bgreenberg-eb/gists{/gist_id}","starred_url":"https://api.github.com/users/bgreenberg-eb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bgreenberg-eb/subscriptions","organizations_url":"https://api.github.com/users/bgreenberg-eb/orgs","repos_url":"https://api.github.com/users/bgreenberg-eb/repos","events_url":"https://api.github.com/users/bgreenberg-eb/events{/privacy}","received_events_url":"https://api.github.com/users/bgreenberg-eb/received_events","type":"User","site_admin":false},"body":"## Overview\r\n\r\nMerge the functionality of the `Router` and `Client`, so that the user no longer needs to initialize a `Router` and call `get_client` before calling actions. The `Client` now stores mini-clients, called `ServiceHandler`s, that incorporate the core request and response functionality for single services.`ServiceHandler`s are lazy-loaded when the service is first called.\r\n\r\n\r\n## API changes\r\n\r\nAll calling methods now take a service name as the first positional argument and use the `ServiceHandler` corresponding to that service name. \r\n\r\nBecause `ServiceHandler`s are self-contained and independent, one `Client` can now make both remote and local service calls. This can greatly simplify client creation and make settings smaller.\r\n\r\n`Client.send_request` signature now matches that of `call_actions` (minus expansions), to make the synchronous and asynchronous APIs more consistent. \r\n\r\n## Test plan\r\n\r\nTests have been updated to reflect the new `Client` interface. Since expansions are now on `call_actions`, I added another test for expansions on multiple actions. I also updated all of the test files that I touched to use `TestCase`-based tests, in accordance with our agreements on test style.\r\n\r\n## Other notes\r\nRemoved the `.arclint` file and added a `.pep8` file.\r\n","created_at":"2017-08-02T21:42:04Z","updated_at":"2017-08-07T21:37:47Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6bdcfa577dae41a9763f5edd24517e250be46c59","assignee":null,"assignees":[],"requested_reviewers":[{"login":"andrewgodwin","id":36182,"avatar_url":"https://avatars1.githubusercontent.com/u/36182?v=4","gravatar_id":"","url":"https://api.github.com/users/andrewgodwin","html_url":"https://github.com/andrewgodwin","followers_url":"https://api.github.com/users/andrewgodwin/followers","following_url":"https://api.github.com/users/andrewgodwin/following{/other_user}","gists_url":"https://api.github.com/users/andrewgodwin/gists{/gist_id}","starred_url":"https://api.github.com/users/andrewgodwin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andrewgodwin/subscriptions","organizations_url":"https://api.github.com/users/andrewgodwin/orgs","repos_url":"https://api.github.com/users/andrewgodwin/repos","events_url":"https://api.github.com/users/andrewgodwin/events{/privacy}","received_events_url":"https://api.github.com/users/andrewgodwin/received_events","type":"User","site_admin":false},{"login":"sfrench","id":65153,"avatar_url":"https://avatars3.githubusercontent.com/u/65153?v=4","gravatar_id":"","url":"https://api.github.com/users/sfrench","html_url":"https://github.com/sfrench","followers_url":"https://api.github.com/users/sfrench/followers","following_url":"https://api.github.com/users/sfrench/following{/other_user}","gists_url":"https://api.github.com/users/sfrench/gists{/gist_id}","starred_url":"https://api.github.com/users/sfrench/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sfrench/subscriptions","organizations_url":"https://api.github.com/users/sfrench/orgs","repos_url":"https://api.github.com/users/sfrench/repos","events_url":"https://api.github.com/users/sfrench/events{/privacy}","received_events_url":"https://api.github.com/users/sfrench/received_events","type":"User","site_admin":false},{"login":"vartec","id":2284726,"avatar_url":"https://avatars0.githubusercontent.com/u/2284726?v=4","gravatar_id":"","url":"https://api.github.com/users/vartec","html_url":"https://github.com/vartec","followers_url":"https://api.github.com/users/vartec/followers","following_url":"https://api.github.com/users/vartec/following{/other_user}","gists_url":"https://api.github.com/users/vartec/gists{/gist_id}","starred_url":"https://api.github.com/users/vartec/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vartec/subscriptions","organizations_url":"https://api.github.com/users/vartec/orgs","repos_url":"https://api.github.com/users/vartec/repos","events_url":"https://api.github.com/users/vartec/events{/privacy}","received_events_url":"https://api.github.com/users/vartec/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/eventbrite/pysoa/pulls/8/commits","review_comments_url":"https://api.github.com/repos/eventbrite/pysoa/pulls/8/comments","review_comment_url":"https://api.github.com/repos/eventbrite/pysoa/pulls/comments{/number}","comments_url":"https://api.github.com/repos/eventbrite/pysoa/issues/8/comments","statuses_url":"https://api.github.com/repos/eventbrite/pysoa/statuses/ffbb6442255c717a1ea41db3486aeb74cd194852","head":{"label":"eventbrite:refactor_client","ref":"refactor_client","sha":"ffbb6442255c717a1ea41db3486aeb74cd194852","user":{"login":"eventbrite","id":369874,"avatar_url":"https://avatars2.githubusercontent.com/u/369874?v=4","gravatar_id":"","url":"https://api.github.com/users/eventbrite","html_url":"https://github.com/eventbrite","followers_url":"https://api.github.com/users/eventbrite/followers","following_url":"https://api.github.com/users/eventbrite/following{/other_user}","gists_url":"https://api.github.com/users/eventbrite/gists{/gist_id}","starred_url":"https://api.github.com/users/eventbrite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eventbrite/subscriptions","organizations_url":"https://api.github.com/users/eventbrite/orgs","repos_url":"https://api.github.com/users/eventbrite/repos","events_url":"https://api.github.com/users/eventbrite/events{/privacy}","received_events_url":"https://api.github.com/users/eventbrite/received_events","type":"Organization","site_admin":false},"repo":{"id":79582901,"name":"pysoa","full_name":"eventbrite/pysoa","owner":{"login":"eventbrite","id":369874,"avatar_url":"https://avatars2.githubusercontent.com/u/369874?v=4","gravatar_id":"","url":"https://api.github.com/users/eventbrite","html_url":"https://github.com/eventbrite","followers_url":"https://api.github.com/users/eventbrite/followers","following_url":"https://api.github.com/users/eventbrite/following{/other_user}","gists_url":"https://api.github.com/users/eventbrite/gists{/gist_id}","starred_url":"https://api.github.com/users/eventbrite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eventbrite/subscriptions","organizations_url":"https://api.github.com/users/eventbrite/orgs","repos_url":"https://api.github.com/users/eventbrite/repos","events_url":"https://api.github.com/users/eventbrite/events{/privacy}","received_events_url":"https://api.github.com/users/eventbrite/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/eventbrite/pysoa","description":"A Python library for writing (micro)services and their clients","fork":false,"url":"https://api.github.com/repos/eventbrite/pysoa","forks_url":"https://api.github.com/repos/eventbrite/pysoa/forks","keys_url":"https://api.github.com/repos/eventbrite/pysoa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/eventbrite/pysoa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/eventbrite/pysoa/teams","hooks_url":"https://api.github.com/repos/eventbrite/pysoa/hooks","issue_events_url":"https://api.github.com/repos/eventbrite/pysoa/issues/events{/number}","events_url":"https://api.github.com/repos/eventbrite/pysoa/events","assignees_url":"https://api.github.com/repos/eventbrite/pysoa/assignees{/user}","branches_url":"https://api.github.com/repos/eventbrite/pysoa/branches{/branch}","tags_url":"https://api.github.com/repos/eventbrite/pysoa/tags","blobs_url":"https://api.github.com/repos/eventbrite/pysoa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/eventbrite/pysoa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/eventbrite/pysoa/git/refs{/sha}","trees_url":"https://api.github.com/repos/eventbrite/pysoa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/eventbrite/pysoa/statuses/{sha}","languages_url":"https://api.github.com/repos/eventbrite/pysoa/languages","stargazers_url":"https://api.github.com/repos/eventbrite/pysoa/stargazers","contributors_url":"https://api.github.com/repos/eventbrite/pysoa/contributors","subscribers_url":"https://api.github.com/repos/eventbrite/pysoa/subscribers","subscription_url":"https://api.github.com/repos/eventbrite/pysoa/subscription","commits_url":"https://api.github.com/repos/eventbrite/pysoa/commits{/sha}","git_commits_url":"https://api.github.com/repos/eventbrite/pysoa/git/commits{/sha}","comments_url":"https://api.github.com/repos/eventbrite/pysoa/comments{/number}","issue_comment_url":"https://api.github.com/repos/eventbrite/pysoa/issues/comments{/number}","contents_url":"https://api.github.com/repos/eventbrite/pysoa/contents/{+path}","compare_url":"https://api.github.com/repos/eventbrite/pysoa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/eventbrite/pysoa/merges","archive_url":"https://api.github.com/repos/eventbrite/pysoa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/eventbrite/pysoa/downloads","issues_url":"https://api.github.com/repos/eventbrite/pysoa/issues{/number}","pulls_url":"https://api.github.com/repos/eventbrite/pysoa/pulls{/number}","milestones_url":"https://api.github.com/repos/eventbrite/pysoa/milestones{/number}","notifications_url":"https://api.github.com/repos/eventbrite/pysoa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/eventbrite/pysoa/labels{/name}","releases_url":"https://api.github.com/repos/eventbrite/pysoa/releases{/id}","deployments_url":"https://api.github.com/repos/eventbrite/pysoa/deployments","created_at":"2017-01-20T17:54:44Z","updated_at":"2017-06-22T21:00:15Z","pushed_at":"2017-08-07T17:44:11Z","git_url":"git://github.com/eventbrite/pysoa.git","ssh_url":"git@github.com:eventbrite/pysoa.git","clone_url":"https://github.com/eventbrite/pysoa.git","svn_url":"https://github.com/eventbrite/pysoa","homepage":"","size":201,"stargazers_count":12,"watchers_count":12,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":12,"default_branch":"master"}},"base":{"label":"eventbrite:master","ref":"master","sha":"a927f358085192e88b343669e9528c465f7151f3","user":{"login":"eventbrite","id":369874,"avatar_url":"https://avatars2.githubusercontent.com/u/369874?v=4","gravatar_id":"","url":"https://api.github.com/users/eventbrite","html_url":"https://github.com/eventbrite","followers_url":"https://api.github.com/users/eventbrite/followers","following_url":"https://api.github.com/users/eventbrite/following{/other_user}","gists_url":"https://api.github.com/users/eventbrite/gists{/gist_id}","starred_url":"https://api.github.com/users/eventbrite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eventbrite/subscriptions","organizations_url":"https://api.github.com/users/eventbrite/orgs","repos_url":"https://api.github.com/users/eventbrite/repos","events_url":"https://api.github.com/users/eventbrite/events{/privacy}","received_events_url":"https://api.github.com/users/eventbrite/received_events","type":"Organization","site_admin":false},"repo":{"id":79582901,"name":"pysoa","full_name":"eventbrite/pysoa","owner":{"login":"eventbrite","id":369874,"avatar_url":"https://avatars2.githubusercontent.com/u/369874?v=4","gravatar_id":"","url":"https://api.github.com/users/eventbrite","html_url":"https://github.com/eventbrite","followers_url":"https://api.github.com/users/eventbrite/followers","following_url":"https://api.github.com/users/eventbrite/following{/other_user}","gists_url":"https://api.github.com/users/eventbrite/gists{/gist_id}","starred_url":"https://api.github.com/users/eventbrite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eventbrite/subscriptions","organizations_url":"https://api.github.com/users/eventbrite/orgs","repos_url":"https://api.github.com/users/eventbrite/repos","events_url":"https://api.github.com/users/eventbrite/events{/privacy}","received_events_url":"https://api.github.com/users/eventbrite/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/eventbrite/pysoa","description":"A Python library for writing (micro)services and their clients","fork":false,"url":"https://api.github.com/repos/eventbrite/pysoa","forks_url":"https://api.github.com/repos/eventbrite/pysoa/forks","keys_url":"https://api.github.com/repos/eventbrite/pysoa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/eventbrite/pysoa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/eventbrite/pysoa/teams","hooks_url":"https://api.github.com/repos/eventbrite/pysoa/hooks","issue_events_url":"https://api.github.com/repos/eventbrite/pysoa/issues/events{/number}","events_url":"https://api.github.com/repos/eventbrite/pysoa/events","assignees_url":"https://api.github.com/repos/eventbrite/pysoa/assignees{/user}","branches_url":"https://api.github.com/repos/eventbrite/pysoa/branches{/branch}","tags_url":"https://api.github.com/repos/eventbrite/pysoa/tags","blobs_url":"https://api.github.com/repos/eventbrite/pysoa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/eventbrite/pysoa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/eventbrite/pysoa/git/refs{/sha}","trees_url":"https://api.github.com/repos/eventbrite/pysoa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/eventbrite/pysoa/statuses/{sha}","languages_url":"https://api.github.com/repos/eventbrite/pysoa/languages","stargazers_url":"https://api.github.com/repos/eventbrite/pysoa/stargazers","contributors_url":"https://api.github.com/repos/eventbrite/pysoa/contributors","subscribers_url":"https://api.github.com/repos/eventbrite/pysoa/subscribers","subscription_url":"https://api.github.com/repos/eventbrite/pysoa/subscription","commits_url":"https://api.github.com/repos/eventbrite/pysoa/commits{/sha}","git_commits_url":"https://api.github.com/repos/eventbrite/pysoa/git/commits{/sha}","comments_url":"https://api.github.com/repos/eventbrite/pysoa/comments{/number}","issue_comment_url":"https://api.github.com/repos/eventbrite/pysoa/issues/comments{/number}","contents_url":"https://api.github.com/repos/eventbrite/pysoa/contents/{+path}","compare_url":"https://api.github.com/repos/eventbrite/pysoa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/eventbrite/pysoa/merges","archive_url":"https://api.github.com/repos/eventbrite/pysoa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/eventbrite/pysoa/downloads","issues_url":"https://api.github.com/repos/eventbrite/pysoa/issues{/number}","pulls_url":"https://api.github.com/repos/eventbrite/pysoa/pulls{/number}","milestones_url":"https://api.github.com/repos/eventbrite/pysoa/milestones{/number}","notifications_url":"https://api.github.com/repos/eventbrite/pysoa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/eventbrite/pysoa/labels{/name}","releases_url":"https://api.github.com/repos/eventbrite/pysoa/releases{/id}","deployments_url":"https://api.github.com/repos/eventbrite/pysoa/deployments","created_at":"2017-01-20T17:54:44Z","updated_at":"2017-06-22T21:00:15Z","pushed_at":"2017-08-07T17:44:11Z","git_url":"git://github.com/eventbrite/pysoa.git","ssh_url":"git@github.com:eventbrite/pysoa.git","clone_url":"https://github.com/eventbrite/pysoa.git","svn_url":"https://github.com/eventbrite/pysoa","homepage":"","size":201,"stargazers_count":12,"watchers_count":12,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":12,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/eventbrite/pysoa/pulls/8"},"html":{"href":"https://github.com/eventbrite/pysoa/pull/8"},"issue":{"href":"https://api.github.com/repos/eventbrite/pysoa/issues/8"},"comments":{"href":"https://api.github.com/repos/eventbrite/pysoa/issues/8/comments"},"review_comments":{"href":"https://api.github.com/repos/eventbrite/pysoa/pulls/8/comments"},"review_comment":{"href":"https://api.github.com/repos/eventbrite/pysoa/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/eventbrite/pysoa/pulls/8/commits"},"statuses":{"href":"https://api.github.com/repos/eventbrite/pysoa/statuses/ffbb6442255c717a1ea41db3486aeb74cd194852"}}}}
{"actor":{"display_login":"sethbrite"}}