{"action":"created","comment":{"url":"https://api.github.com/repos/level12/keg-bouncer/pulls/comments/51461355","id":51461355,"diff_hunk":"@@ -21,3 +24,68 @@ def link_user_to_user_groups(op, user_primary_key_column):\n \n def drop_link_from_user_to_user_groups(op):\n     op.drop_table(user_user_group_link_table_name)\n+\n+\n+def alembic_create_permission(op, permissions=[{}], groups=set(), bundles=set()):\n+    \"\"\"Bulk create permissions and add them to groups and bundles\n+\n+    :param permisisons: a list of dictionaries with 'token' and 'description' keys\n+    :param groups: a set of group labels, missing groups will be created\n+    :param bundles: a set of bundle labels, missing bundles will be created\n+    \"\"\"\n+\n+    new_permission_tokens = set([perm['token'] for perm in permissions])\n+    groups = set(groups)\n+    bundles = set(bundles)\n+    con = op.get_bind()\n+    meta = sa.MetaData()\n+    execute = con.execute\n+\n+    # inspect the tables we need and create a table object for them\n+    permissions_table = sa.Table('keg_bouncer_permissions', meta, autoload=True,\n+                                 autoload_with=con.engine)\n+\n+    groups_table = sa.Table('keg_bouncer_user_groups', meta, autoload=True,\n+                            autoload_with=con.engine)\n+\n+    groups_map = sa.Table('keg_bouncer_user_group_permission_map', meta, autoload=True,\n+                          autoload_with=con.engine)\n+\n+    bundles_table = sa.Table('keg_bouncer_permission_bundles', meta, autoload=True,\n+                             autoload_with=con.engine)\n+\n+    bundles_map = sa.Table('keg_bouncer_bundle_permission_map', meta, autoload=True,\n+                           autoload_with=con.engine)\n+\n+    # Insert all of the new permissions\n+    op.bulk_insert(permissions_table, permissions)\n+\n+    new_perm_query = sa.select(\n+        [permissions_table.c.id]\n+    ).where(permissions_table.c.token.in_(new_permission_tokens))\n+\n+    new_permission_ids = [permission.id for permission in con.execute(new_perm_query)]\n+\n+    def get_or_create_relationships(table, relation_data):\n+        # Get the exiting relationship objects\n+        existing_query = sa.select([table.c.label]).where(table.c.label.in_(relation_data))\n+        existing = set([label[0] for label in execute(existing_query)])\n+\n+        # Insert any new ones\n+        op.bulk_insert(table,  [{'label': label} for label in relation_data.difference(existing)])\n+\n+        # return the ids of all the related entities, created or not for later linking\n+        return [result.id for result in\n+                execute(sa.select([table.c.id]).where(table.c.label.in_(relation_data)))]\n+\n+    if len(groups) != 0:\n+        group_ids = get_or_create_relationships(groups_table, groups)\n+        mapped_values = [{'permission_id': k, 'user_group_id': v}\n+                         for k, v in itertools.product(new_permission_ids, group_ids)]\n+        op.bulk_insert(groups_map, mapped_values)\n+\n+    if len(bundles) != 0:\n+        group_ids = get_or_create_relationships(bundles_table, bundles)","path":"keg_bouncer/model/migration.py","position":77,"original_position":77,"commit_id":"bf98782b84cc5dc29af9894d8088a3cffbf5350d","original_commit_id":"bf98782b84cc5dc29af9894d8088a3cffbf5350d","user":{"login":"3noch","id":882455,"avatar_url":"https://avatars.githubusercontent.com/u/882455?v=3","gravatar_id":"","url":"https://api.github.com/users/3noch","html_url":"https://github.com/3noch","followers_url":"https://api.github.com/users/3noch/followers","following_url":"https://api.github.com/users/3noch/following{/other_user}","gists_url":"https://api.github.com/users/3noch/gists{/gist_id}","starred_url":"https://api.github.com/users/3noch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/3noch/subscriptions","organizations_url":"https://api.github.com/users/3noch/orgs","repos_url":"https://api.github.com/users/3noch/repos","events_url":"https://api.github.com/users/3noch/events{/privacy}","received_events_url":"https://api.github.com/users/3noch/received_events","type":"User","site_admin":false},"body":"`bundle_ids`","created_at":"2016-02-01T18:48:29Z","updated_at":"2016-02-01T18:48:29Z","html_url":"https://github.com/level12/keg-bouncer/pull/6#discussion_r51461355","pull_request_url":"https://api.github.com/repos/level12/keg-bouncer/pulls/6","_links":{"self":{"href":"https://api.github.com/repos/level12/keg-bouncer/pulls/comments/51461355"},"html":{"href":"https://github.com/level12/keg-bouncer/pull/6#discussion_r51461355"},"pull_request":{"href":"https://api.github.com/repos/level12/keg-bouncer/pulls/6"}}},"pull_request":{"url":"https://api.github.com/repos/level12/keg-bouncer/pulls/6","id":57878177,"html_url":"https://github.com/level12/keg-bouncer/pull/6","diff_url":"https://github.com/level12/keg-bouncer/pull/6.diff","patch_url":"https://github.com/level12/keg-bouncer/pull/6.patch","issue_url":"https://api.github.com/repos/level12/keg-bouncer/issues/6","number":6,"state":"open","locked":false,"title":"Create an alembic shortcut to create permissions","user":{"login":"nZac","id":276212,"avatar_url":"https://avatars.githubusercontent.com/u/276212?v=3","gravatar_id":"","url":"https://api.github.com/users/nZac","html_url":"https://github.com/nZac","followers_url":"https://api.github.com/users/nZac/followers","following_url":"https://api.github.com/users/nZac/following{/other_user}","gists_url":"https://api.github.com/users/nZac/gists{/gist_id}","starred_url":"https://api.github.com/users/nZac/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nZac/subscriptions","organizations_url":"https://api.github.com/users/nZac/orgs","repos_url":"https://api.github.com/users/nZac/repos","events_url":"https://api.github.com/users/nZac/events{/privacy}","received_events_url":"https://api.github.com/users/nZac/received_events","type":"User","site_admin":false},"body":"This will create a permission, create any groups, and create any bundles.  Feedback welcome.","created_at":"2016-02-01T18:16:51Z","updated_at":"2016-02-01T18:48:29Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1b7688ce9f26ecfbd65c415a1825e1855ab4ed00","assignee":{"login":"3noch","id":882455,"avatar_url":"https://avatars.githubusercontent.com/u/882455?v=3","gravatar_id":"","url":"https://api.github.com/users/3noch","html_url":"https://github.com/3noch","followers_url":"https://api.github.com/users/3noch/followers","following_url":"https://api.github.com/users/3noch/following{/other_user}","gists_url":"https://api.github.com/users/3noch/gists{/gist_id}","starred_url":"https://api.github.com/users/3noch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/3noch/subscriptions","organizations_url":"https://api.github.com/users/3noch/orgs","repos_url":"https://api.github.com/users/3noch/repos","events_url":"https://api.github.com/users/3noch/events{/privacy}","received_events_url":"https://api.github.com/users/3noch/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/level12/keg-bouncer/pulls/6/commits","review_comments_url":"https://api.github.com/repos/level12/keg-bouncer/pulls/6/comments","review_comment_url":"https://api.github.com/repos/level12/keg-bouncer/pulls/comments{/number}","comments_url":"https://api.github.com/repos/level12/keg-bouncer/issues/6/comments","statuses_url":"https://api.github.com/repos/level12/keg-bouncer/statuses/bf98782b84cc5dc29af9894d8088a3cffbf5350d","head":{"label":"level12:gh-5-create-permission-helper","ref":"gh-5-create-permission-helper","sha":"bf98782b84cc5dc29af9894d8088a3cffbf5350d","user":{"login":"level12","id":11673919,"avatar_url":"https://avatars.githubusercontent.com/u/11673919?v=3","gravatar_id":"","url":"https://api.github.com/users/level12","html_url":"https://github.com/level12","followers_url":"https://api.github.com/users/level12/followers","following_url":"https://api.github.com/users/level12/following{/other_user}","gists_url":"https://api.github.com/users/level12/gists{/gist_id}","starred_url":"https://api.github.com/users/level12/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/level12/subscriptions","organizations_url":"https://api.github.com/users/level12/orgs","repos_url":"https://api.github.com/users/level12/repos","events_url":"https://api.github.com/users/level12/events{/privacy}","received_events_url":"https://api.github.com/users/level12/received_events","type":"Organization","site_admin":false},"repo":{"id":47431759,"name":"keg-bouncer","full_name":"level12/keg-bouncer","owner":{"login":"level12","id":11673919,"avatar_url":"https://avatars.githubusercontent.com/u/11673919?v=3","gravatar_id":"","url":"https://api.github.com/users/level12","html_url":"https://github.com/level12","followers_url":"https://api.github.com/users/level12/followers","following_url":"https://api.github.com/users/level12/following{/other_user}","gists_url":"https://api.github.com/users/level12/gists{/gist_id}","starred_url":"https://api.github.com/users/level12/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/level12/subscriptions","organizations_url":"https://api.github.com/users/level12/orgs","repos_url":"https://api.github.com/users/level12/repos","events_url":"https://api.github.com/users/level12/events{/privacy}","received_events_url":"https://api.github.com/users/level12/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/level12/keg-bouncer","description":"A three-tiered permissions model for KegElements building atop Flask-User","fork":false,"url":"https://api.github.com/repos/level12/keg-bouncer","forks_url":"https://api.github.com/repos/level12/keg-bouncer/forks","keys_url":"https://api.github.com/repos/level12/keg-bouncer/keys{/key_id}","collaborators_url":"https://api.github.com/repos/level12/keg-bouncer/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/level12/keg-bouncer/teams","hooks_url":"https://api.github.com/repos/level12/keg-bouncer/hooks","issue_events_url":"https://api.github.com/repos/level12/keg-bouncer/issues/events{/number}","events_url":"https://api.github.com/repos/level12/keg-bouncer/events","assignees_url":"https://api.github.com/repos/level12/keg-bouncer/assignees{/user}","branches_url":"https://api.github.com/repos/level12/keg-bouncer/branches{/branch}","tags_url":"https://api.github.com/repos/level12/keg-bouncer/tags","blobs_url":"https://api.github.com/repos/level12/keg-bouncer/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/level12/keg-bouncer/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/level12/keg-bouncer/git/refs{/sha}","trees_url":"https://api.github.com/repos/level12/keg-bouncer/git/trees{/sha}","statuses_url":"https://api.github.com/repos/level12/keg-bouncer/statuses/{sha}","languages_url":"https://api.github.com/repos/level12/keg-bouncer/languages","stargazers_url":"https://api.github.com/repos/level12/keg-bouncer/stargazers","contributors_url":"https://api.github.com/repos/level12/keg-bouncer/contributors","subscribers_url":"https://api.github.com/repos/level12/keg-bouncer/subscribers","subscription_url":"https://api.github.com/repos/level12/keg-bouncer/subscription","commits_url":"https://api.github.com/repos/level12/keg-bouncer/commits{/sha}","git_commits_url":"https://api.github.com/repos/level12/keg-bouncer/git/commits{/sha}","comments_url":"https://api.github.com/repos/level12/keg-bouncer/comments{/number}","issue_comment_url":"https://api.github.com/repos/level12/keg-bouncer/issues/comments{/number}","contents_url":"https://api.github.com/repos/level12/keg-bouncer/contents/{+path}","compare_url":"https://api.github.com/repos/level12/keg-bouncer/compare/{base}...{head}","merges_url":"https://api.github.com/repos/level12/keg-bouncer/merges","archive_url":"https://api.github.com/repos/level12/keg-bouncer/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/level12/keg-bouncer/downloads","issues_url":"https://api.github.com/repos/level12/keg-bouncer/issues{/number}","pulls_url":"https://api.github.com/repos/level12/keg-bouncer/pulls{/number}","milestones_url":"https://api.github.com/repos/level12/keg-bouncer/milestones{/number}","notifications_url":"https://api.github.com/repos/level12/keg-bouncer/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/level12/keg-bouncer/labels{/name}","releases_url":"https://api.github.com/repos/level12/keg-bouncer/releases{/id}","deployments_url":"https://api.github.com/repos/level12/keg-bouncer/deployments","created_at":"2015-12-04T22:22:53Z","updated_at":"2016-01-12T22:26:34Z","pushed_at":"2016-02-01T18:16:52Z","git_url":"git://github.com/level12/keg-bouncer.git","ssh_url":"git@github.com:level12/keg-bouncer.git","clone_url":"https://github.com/level12/keg-bouncer.git","svn_url":"https://github.com/level12/keg-bouncer","homepage":"","size":12834,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":2,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"base":{"label":"level12:master","ref":"master","sha":"a7516e3768d7b75fc7f75bc4701649f9c3c8561a","user":{"login":"level12","id":11673919,"avatar_url":"https://avatars.githubusercontent.com/u/11673919?v=3","gravatar_id":"","url":"https://api.github.com/users/level12","html_url":"https://github.com/level12","followers_url":"https://api.github.com/users/level12/followers","following_url":"https://api.github.com/users/level12/following{/other_user}","gists_url":"https://api.github.com/users/level12/gists{/gist_id}","starred_url":"https://api.github.com/users/level12/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/level12/subscriptions","organizations_url":"https://api.github.com/users/level12/orgs","repos_url":"https://api.github.com/users/level12/repos","events_url":"https://api.github.com/users/level12/events{/privacy}","received_events_url":"https://api.github.com/users/level12/received_events","type":"Organization","site_admin":false},"repo":{"id":47431759,"name":"keg-bouncer","full_name":"level12/keg-bouncer","owner":{"login":"level12","id":11673919,"avatar_url":"https://avatars.githubusercontent.com/u/11673919?v=3","gravatar_id":"","url":"https://api.github.com/users/level12","html_url":"https://github.com/level12","followers_url":"https://api.github.com/users/level12/followers","following_url":"https://api.github.com/users/level12/following{/other_user}","gists_url":"https://api.github.com/users/level12/gists{/gist_id}","starred_url":"https://api.github.com/users/level12/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/level12/subscriptions","organizations_url":"https://api.github.com/users/level12/orgs","repos_url":"https://api.github.com/users/level12/repos","events_url":"https://api.github.com/users/level12/events{/privacy}","received_events_url":"https://api.github.com/users/level12/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/level12/keg-bouncer","description":"A three-tiered permissions model for KegElements building atop Flask-User","fork":false,"url":"https://api.github.com/repos/level12/keg-bouncer","forks_url":"https://api.github.com/repos/level12/keg-bouncer/forks","keys_url":"https://api.github.com/repos/level12/keg-bouncer/keys{/key_id}","collaborators_url":"https://api.github.com/repos/level12/keg-bouncer/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/level12/keg-bouncer/teams","hooks_url":"https://api.github.com/repos/level12/keg-bouncer/hooks","issue_events_url":"https://api.github.com/repos/level12/keg-bouncer/issues/events{/number}","events_url":"https://api.github.com/repos/level12/keg-bouncer/events","assignees_url":"https://api.github.com/repos/level12/keg-bouncer/assignees{/user}","branches_url":"https://api.github.com/repos/level12/keg-bouncer/branches{/branch}","tags_url":"https://api.github.com/repos/level12/keg-bouncer/tags","blobs_url":"https://api.github.com/repos/level12/keg-bouncer/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/level12/keg-bouncer/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/level12/keg-bouncer/git/refs{/sha}","trees_url":"https://api.github.com/repos/level12/keg-bouncer/git/trees{/sha}","statuses_url":"https://api.github.com/repos/level12/keg-bouncer/statuses/{sha}","languages_url":"https://api.github.com/repos/level12/keg-bouncer/languages","stargazers_url":"https://api.github.com/repos/level12/keg-bouncer/stargazers","contributors_url":"https://api.github.com/repos/level12/keg-bouncer/contributors","subscribers_url":"https://api.github.com/repos/level12/keg-bouncer/subscribers","subscription_url":"https://api.github.com/repos/level12/keg-bouncer/subscription","commits_url":"https://api.github.com/repos/level12/keg-bouncer/commits{/sha}","git_commits_url":"https://api.github.com/repos/level12/keg-bouncer/git/commits{/sha}","comments_url":"https://api.github.com/repos/level12/keg-bouncer/comments{/number}","issue_comment_url":"https://api.github.com/repos/level12/keg-bouncer/issues/comments{/number}","contents_url":"https://api.github.com/repos/level12/keg-bouncer/contents/{+path}","compare_url":"https://api.github.com/repos/level12/keg-bouncer/compare/{base}...{head}","merges_url":"https://api.github.com/repos/level12/keg-bouncer/merges","archive_url":"https://api.github.com/repos/level12/keg-bouncer/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/level12/keg-bouncer/downloads","issues_url":"https://api.github.com/repos/level12/keg-bouncer/issues{/number}","pulls_url":"https://api.github.com/repos/level12/keg-bouncer/pulls{/number}","milestones_url":"https://api.github.com/repos/level12/keg-bouncer/milestones{/number}","notifications_url":"https://api.github.com/repos/level12/keg-bouncer/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/level12/keg-bouncer/labels{/name}","releases_url":"https://api.github.com/repos/level12/keg-bouncer/releases{/id}","deployments_url":"https://api.github.com/repos/level12/keg-bouncer/deployments","created_at":"2015-12-04T22:22:53Z","updated_at":"2016-01-12T22:26:34Z","pushed_at":"2016-02-01T18:16:52Z","git_url":"git://github.com/level12/keg-bouncer.git","ssh_url":"git@github.com:level12/keg-bouncer.git","clone_url":"https://github.com/level12/keg-bouncer.git","svn_url":"https://github.com/level12/keg-bouncer","homepage":"","size":12834,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":2,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/level12/keg-bouncer/pulls/6"},"html":{"href":"https://github.com/level12/keg-bouncer/pull/6"},"issue":{"href":"https://api.github.com/repos/level12/keg-bouncer/issues/6"},"comments":{"href":"https://api.github.com/repos/level12/keg-bouncer/issues/6/comments"},"review_comments":{"href":"https://api.github.com/repos/level12/keg-bouncer/pulls/6/comments"},"review_comment":{"href":"https://api.github.com/repos/level12/keg-bouncer/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/level12/keg-bouncer/pulls/6/commits"},"statuses":{"href":"https://api.github.com/repos/level12/keg-bouncer/statuses/bf98782b84cc5dc29af9894d8088a3cffbf5350d"}}}}