You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by mr...@apache.org on 2016/08/01 16:54:03 UTC

[28/50] [abbrv] usergrid git commit: Initial checkin for Python Utilities and SDK

Initial checkin for Python Utilities and SDK


Project: http://git-wip-us.apache.org/repos/asf/usergrid/repo
Commit: http://git-wip-us.apache.org/repos/asf/usergrid/commit/32f9e55d
Tree: http://git-wip-us.apache.org/repos/asf/usergrid/tree/32f9e55d
Diff: http://git-wip-us.apache.org/repos/asf/usergrid/diff/32f9e55d

Branch: refs/heads/master
Commit: 32f9e55da01c92dbda81a88a465f088405259faa
Parents: 91abfd8
Author: Jeff West <jw...@apigee.com>
Authored: Tue Jul 26 13:26:36 2016 -0700
Committer: Jeff West <jw...@apigee.com>
Committed: Tue Jul 26 13:26:36 2016 -0700

----------------------------------------------------------------------
 sdks/python/.gitignore                          |   57 +
 sdks/python/LICENSE                             |  202 ++
 sdks/python/README.md                           |   16 +
 sdks/python/sample_app.py                       |   68 +
 sdks/python/setup.py                            |   39 +
 sdks/python/usergrid/UsergridApplication.py     |   62 +
 sdks/python/usergrid/UsergridAuth.py            |  103 +
 sdks/python/usergrid/UsergridClient.py          |  398 ++++
 sdks/python/usergrid/UsergridCollection.py      |   77 +
 sdks/python/usergrid/UsergridConnection.py      |   26 +
 sdks/python/usergrid/UsergridError.py           |   17 +
 sdks/python/usergrid/UsergridOrganization.py    |   31 +
 sdks/python/usergrid/UsergridQueryIterator.py   |  155 ++
 sdks/python/usergrid/__init__.py                |   37 +
 sdks/python/usergrid/app_templates.py           |   36 +
 sdks/python/usergrid/management_templates.py    |   25 +
 utils/usergrid-util-python/.gitignore           |   61 +
 utils/usergrid-util-python/LICENSE              |   22 +
 utils/usergrid-util-python/README.md            |   15 +
 .../es_tools/alias_mover.py                     |  129 ++
 .../es_tools/cluster_shard_allocation.py        |   89 +
 .../es_tools/command_sender.py                  |   42 +
 .../es_tools/es_index_iterator_reindexer.py     |  107 +
 .../es_tools/es_searcher.py                     |   24 +
 .../es_tools/index_deleter.py                   |   68 +
 .../es_tools/index_iterator_size_checker.py     |  270 +++
 .../es_tools/index_prefix_checker.py            |   81 +
 .../es_tools/index_replica_setter.py            |  118 +
 .../es_tools/index_shard_allocator.py           |  148 ++
 .../es_tools/mapping_deleter.py                 |   34 +
 .../es_tools/mapping_retriever.py               |   45 +
 .../es_tools/monitor_tasks.py                   |   41 +
 utils/usergrid-util-python/index_test/README.md |    1 +
 .../index_test/document_creator.py              |  254 ++
 .../index_test/index_test_mixed_batch.py        |  545 +++++
 .../index_test/index_test_single_type_batch.py  |  547 +++++
 utils/usergrid-util-python/requirements.txt     |    4 +
 .../activity_streams/activity_streams.py        |  132 ++
 .../samples/beacon-event-example.py             |  196 ++
 .../samples/counter_test.py                     |   31 +
 utils/usergrid-util-python/setup.py             |   40 +
 .../usergrid_tools/__init__.py                  |    4 +
 .../usergrid_tools/general/__init__.py          |    0
 .../usergrid_tools/general/deleter.py           |  151 ++
 .../general/duplicate_name_checker.py           |   25 +
 .../usergrid_tools/general/queue_monitor.py     |  119 +
 .../usergrid_tools/general/url_tester.py        |   87 +
 .../general/user_confirm_activate.py            |   29 +
 .../usergrid_tools/general/user_creator.py      |   49 +
 .../usergrid_tools/groups/__init__.py           |    0
 .../usergrid_tools/groups/big_group_creater.py  |   86 +
 .../usergrid_tools/indexing/README.md           |   22 +
 .../usergrid_tools/indexing/__init__.py         |    0
 .../usergrid_tools/indexing/batch_index_test.py |  340 +++
 .../indexing/entity_index_test.py               |  317 +++
 .../usergrid_tools/iterators/README.md          |    8 +
 .../usergrid_tools/iterators/__init__.py        |    0
 .../usergrid_tools/iterators/simple_iterator.py |   79 +
 .../iterators/usergrid_cross_region_iterator.py |  409 ++++
 .../usergrid_tools/library_check.py             |   23 +
 .../usergrid_tools/migration/README.md          |  234 ++
 .../usergrid_tools/migration/__init__.py        |    2 +
 .../migration/usergrid_data_exporter.py         |  923 ++++++++
 .../migration/usergrid_data_migrator.py         | 2168 ++++++++++++++++++
 .../usergrid_tools/parse_importer/README.md     |   90 +
 .../usergrid_tools/parse_importer/__init__.py   |    0
 .../parse_importer/parse_importer.py            |  385 ++++
 .../usergrid_tools/permissions/README.md        |    3 +
 .../usergrid_tools/permissions/permissions.py   |  146 ++
 .../usergrid_tools/queue/README.md              |    1 +
 .../queue/dlq-iterator-checker.py               |  143 ++
 .../usergrid_tools/queue/dlq_requeue.py         |  173 ++
 .../queue/queue-config-sample.json              |   22 +
 .../usergrid_tools/queue/queue_cleaner.py       |  155 ++
 .../usergrid_tools/redis/redis_iterator.py      |   30 +
 .../usergrid_tools/redis/redisscan.py           |   15 +
 76 files changed, 10631 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/.gitignore
----------------------------------------------------------------------
diff --git a/sdks/python/.gitignore b/sdks/python/.gitignore
new file mode 100644
index 0000000..ba74660
--- /dev/null
+++ b/sdks/python/.gitignore
@@ -0,0 +1,57 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/LICENSE
----------------------------------------------------------------------
diff --git a/sdks/python/LICENSE b/sdks/python/LICENSE
new file mode 100755
index 0000000..8f71f43
--- /dev/null
+++ b/sdks/python/LICENSE
@@ -0,0 +1,202 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/README.md
----------------------------------------------------------------------
diff --git a/sdks/python/README.md b/sdks/python/README.md
new file mode 100755
index 0000000..cc2af97
--- /dev/null
+++ b/sdks/python/README.md
@@ -0,0 +1,16 @@
+# Usergrid Python SDK
+
+# Overview
+This is a starter project for the Usergrid Python SDK.  It is a work in progress.
+
+# Installation
+
+## PIP (http://pip.readthedocs.org/en/stable/installing/)
+
+`pip install usergrid`
+
+## Manual installation
+
+- `git clone git@github.com:jwest-apigee/usergrid-python.git`
+- `cd usergrid-python`
+- `pip install -e .`
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/sample_app.py
----------------------------------------------------------------------
diff --git a/sdks/python/sample_app.py b/sdks/python/sample_app.py
new file mode 100755
index 0000000..a829736
--- /dev/null
+++ b/sdks/python/sample_app.py
@@ -0,0 +1,68 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+from usergrid import Usergrid
+
+__author__ = 'ApigeeCorporation'
+
+
+def main():
+    Usergrid.init(org_id='jwest1',
+                  app_id='sandbox')
+
+    response = Usergrid.DELETE('pets', 'max')
+    if not response.ok:
+        print 'Failed to delete max: %s' % response
+        exit()
+
+    response = Usergrid.DELETE('owners', 'jeff')
+    if not response.ok:
+        print 'Failed to delete Jeff: %s' % response
+        exit()
+
+    response = Usergrid.POST('pets', {'name': 'max'})
+
+    if response.ok:
+        pet = response.first()
+        print pet
+        response = Usergrid.POST('owners', {'name': 'jeff'})
+
+        if response.ok:
+            owner = response.first()
+            print owner
+            response = pet.connect('ownedBy', owner)
+
+            if response.ok:
+                print 'Connected!'
+
+                response = pet.disconnect('ownedBy', owner)
+
+                if response.ok:
+                    print 'all done!'
+                else:
+                    print response
+            else:
+                print 'failed to connect: %s' % response
+
+        else:
+            print 'Failed to create Jeff: %s' % response
+
+    else:
+        print response
+
+
+main()

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/setup.py
----------------------------------------------------------------------
diff --git a/sdks/python/setup.py b/sdks/python/setup.py
new file mode 100755
index 0000000..6eec51f
--- /dev/null
+++ b/sdks/python/setup.py
@@ -0,0 +1,39 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+__author__ = 'Jeff West @ ApigeeCorporation'
+
+from setuptools import setup, find_packages
+
+VERSION = '0.1.11'
+
+setup(
+        name='usergrid',
+        version=VERSION,
+        description='Usergrid SDK for Python',
+        url='http://usergrid.apache.org',
+        download_url="https://codeload.github.com/jwest-apigee/usergrid-python/zip/v" + VERSION,
+        author='Jeff West',
+        author_email='jwest@apigee.com',
+        packages=find_packages(),
+        install_requires=[
+            'requests',
+            'urllib3'
+        ],
+        entry_points={
+        }
+)

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/usergrid/UsergridApplication.py
----------------------------------------------------------------------
diff --git a/sdks/python/usergrid/UsergridApplication.py b/sdks/python/usergrid/UsergridApplication.py
new file mode 100644
index 0000000..cedd5b1
--- /dev/null
+++ b/sdks/python/usergrid/UsergridApplication.py
@@ -0,0 +1,62 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+import logging
+from usergrid import UsergridError, UsergridCollection
+from usergrid.app_templates import app_url_template
+
+
+class UsergridApplication(object):
+    def __init__(self, app_id, client):
+        self.app_id = app_id
+        self.client = client
+        self.logger = logging.getLogger('usergrid.UsergridClient')
+
+    def list_collections(self):
+        url = app_url_template.format(app_id=self.app_id,
+                                      **self.client.url_data)
+        r = self.client.get(url)
+
+        if r.status_code == 200:
+            api_response = r.json()
+            collection_list = api_response.get('entities')[0].get('metadata', {}).get('collections', {})
+            collections = {}
+
+            for collection_name in collection_list:
+                collections[collection_name] = UsergridCollection(self.client.org_id,
+                                                                  self.app_id,
+                                                                  collection_name,
+                                                                  self.client)
+
+            return collections
+
+        else:
+            raise UsergridError(message='Unable to post to list collections',
+                                status_code=r.status_code,
+                                api_response=r,
+                                url=url)
+
+    def collection(self, collection_name):
+        return UsergridCollection(self.client.org_id,
+                                  self.app_id,
+                                  collection_name,
+                                  self.client)
+
+    def authenticate_app_client(self,
+                                **kwargs):
+
+        return self.client.authenticate_app_client(self.app_id, **kwargs)

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/usergrid/UsergridAuth.py
----------------------------------------------------------------------
diff --git a/sdks/python/usergrid/UsergridAuth.py b/sdks/python/usergrid/UsergridAuth.py
new file mode 100644
index 0000000..3406312
--- /dev/null
+++ b/sdks/python/usergrid/UsergridAuth.py
@@ -0,0 +1,103 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+import json
+import requests
+from usergrid.management_templates import org_token_url_template
+
+
+class UsergridAuth:
+    def __init__(self,
+                 grant_type,
+                 url_template,
+                 username=None,
+                 password=None,
+                 client_id=None,
+                 client_secret=None,
+                 token_ttl_seconds=86400):
+
+        self.grant_type = grant_type
+        self.username = username
+        self.password = password
+        self.client_id = client_id
+        self.client_secret = client_secret
+        self.token_ttl_seconds = token_ttl_seconds
+        self.url_template = url_template
+        self.access_token = None
+
+    def get_token_request(self):
+        if self.grant_type == 'client_credentials':
+            return {
+                'grant_type': 'client_credentials',
+                'client_id': self.client_id,
+                'client_secret': self.client_secret,
+                'ttl': self.token_ttl_seconds * 1000
+            }
+        elif self.grant_type == 'password':
+            return {
+                'grant_type': 'password',
+                'username': self.username,
+                'password': self.password,
+                'ttl': self.token_ttl_seconds * 1000
+            }
+
+        else:
+            raise ValueError('Unspecified/unknown grant type: %s' % self.grant_type)
+
+    def authenticate(self, client):
+        token_request = self.get_token_request()
+
+        url = self.url_template.format(**client.url_data)
+
+        r = requests.post(url, data=json.dumps(token_request))
+
+        if r.status_code == 200:
+            response = r.json()
+            self.access_token = response.get('access_token')
+
+        else:
+            raise ValueError('Unable to authenticate: %s' % r.text)
+
+
+class UsergridOrgAuth(UsergridAuth):
+    def __init__(self, client_id, client_secret, token_ttl_seconds=86400):
+        UsergridAuth.__init__(self,
+                              grant_type='client_credentials',
+                              url_template=org_token_url_template,
+                              client_id=client_id,
+                              client_secret=client_secret,
+                              token_ttl_seconds=token_ttl_seconds)
+
+
+class UsergridAppAuth(UsergridAuth):
+    def __init__(self, client_id, client_secret, token_ttl_seconds=86400):
+        UsergridAuth.__init__(self,
+                              grant_type='client_credentials',
+                              url_template=app_token_url_template,
+                              client_id=client_id,
+                              client_secret=client_secret,
+                              token_ttl_seconds=token_ttl_seconds)
+
+
+class UsergridUserAuth(UsergridAuth):
+    def __init__(self, username, password, token_ttl_seconds=86400):
+        UsergridAuth.__init__(self,
+                              grant_type='password',
+                              url_template=app_token_url_template,
+                              username=username,
+                              password=password,
+                              token_ttl_seconds=token_ttl_seconds)

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/usergrid/UsergridClient.py
----------------------------------------------------------------------
diff --git a/sdks/python/usergrid/UsergridClient.py b/sdks/python/usergrid/UsergridClient.py
new file mode 100644
index 0000000..2ab8f73
--- /dev/null
+++ b/sdks/python/usergrid/UsergridClient.py
@@ -0,0 +1,398 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+import json
+import logging
+import requests
+from usergrid.UsergridAuth import UsergridAppAuth
+from usergrid.app_templates import get_entity_url_template, post_collection_url_template, put_entity_url_template, \
+    delete_entity_url_template, connect_entities_by_type_template, assign_role_url_template
+
+
+def value_error(message):
+    raise ValueError(message)
+
+
+def usergrid_error(r):
+    pass
+
+
+class Usergrid(object):
+    client = None
+
+    @staticmethod
+    def init(org_id,
+             app_id,
+             **kwargs):
+        Usergrid.client = UsergridClient(org_id, app_id, **kwargs)
+
+    @staticmethod
+    def GET(collection, uuid_name, **kwargs):
+        return Usergrid.client.GET(collection, uuid_name, **kwargs)
+
+    @staticmethod
+    def PUT(collection, uuid_name, data, **kwargs):
+        return Usergrid.client.PUT(collection, uuid_name, data, **kwargs)
+
+    @staticmethod
+    def POST(collection, data, **kwargs):
+        return Usergrid.client.POST(collection, data, **kwargs)
+
+    @staticmethod
+    def DELETE(collection, uuid_name, **kwargs):
+        return Usergrid.client.DELETE(collection, uuid_name, **kwargs)
+
+    @staticmethod
+    def connect_entities(from_entity, relationship, to_entity, **kwargs):
+        return Usergrid.client.connect_entities(from_entity, relationship, to_entity, **kwargs)
+
+    @staticmethod
+    def disconnect_entities(from_entity, relationship, to_entity, **kwargs):
+        return Usergrid.client.disconnect_entities(from_entity, relationship, to_entity, **kwargs)
+
+    @staticmethod
+    def assign_role(role_uuid_name, user_entity, **kwargs):
+        return Usergrid.client.assign_role(role_uuid_name, user_entity, **kwargs)
+
+
+class UsergridResponse(object):
+    def __init__(self, api_response, client):
+        self.api_response = api_response
+        self.client = client
+
+        if api_response is None:
+            self.ok = False
+            self.body = 'No Response'
+
+        else:
+            self.headers = api_response.headers
+
+            if api_response.status_code == 200:
+                self.ok = True
+                self.body = api_response.json()
+                self.entities = self.body.get('entities', [])
+
+            else:
+                self.ok = False
+
+                if api_response.headers.get('Content-type') == 'application/json':
+                    self.body = api_response.json()
+                else:
+                    self.body = 'HTTP %s: %s' % (api_response.status_code, api_response.text)
+
+    def __str__(self):
+        return json.dumps(self.body)
+
+    def first(self):
+        return UsergridEntity(entity_data=self.entities[0]) if self.ok and self.entities and len(
+                self.entities) > 0 else None
+
+    def entity(self):
+        return self.first()
+
+    def last(self):
+        return UsergridEntity(entity_data=self.entities[len(self.entities) - 1]) if self.ok and self.entities and len(
+                self.entities) > 0 else None
+
+    def has_next_page(self):
+        return 'cursor' in self.body if self.ok else False
+
+
+class UsergridEntity(object):
+    def __init__(self, entity_data):
+        self.entity_data = entity_data
+
+    def __str__(self):
+        return json.dumps(self.entity_data)
+
+    def get(self, name, default=None):
+        return self.entity_data.get(name, default)
+
+    def entity_id(self):
+
+        if self.entity_data.get('type', '').lower() in ['users', 'user']:
+            return self.entity_data.get('uuid', self.entity_data.get('username'))
+
+        return self.entity_data.get('uuid', self.entity_data.get('name'))
+
+    def can_mutate_or_load(self):
+        entity_id = self.entity_id()
+
+        if entity_id is None or self.entity_data.get('type') is None:
+            return False
+
+        return True
+
+    def put_property(self, name, value):
+        self.entity_data[name] = value
+
+    def put_properties(self, properties):
+        if isinstance(properties, dict):
+            self.entity_data.update(properties)
+
+    def remove_property(self, name):
+
+        if name is not None and name in self.entity_data:
+            del self.entity_data[name]
+
+    def remove_properties(self, properties):
+        if isinstance(properties, (list, dict)):
+            for property_name in properties:
+                self.remove_property(property_name)
+
+    def append(self, array_name, value):
+        if array_name in self.entity_data:
+            if isinstance(self.entity_data[array_name], list):
+                self.entity_data[array_name].append(value)
+        else:
+            self.entity_data[array_name] = [value]
+
+    def prepend(self, array_name, value):
+        if array_name in self.entity_data:
+            if isinstance(self.entity_data[array_name], list):
+                self.entity_data[array_name].pre(value)
+        else:
+            self.entity_data[array_name] = [value]
+
+    def insert(self, array_name, value, index):
+        if array_name in self.entity_data:
+            if isinstance(self.entity_data[array_name], list):
+                self.entity_data[array_name].insert(index, value)
+
+    def shift(self, array_name):
+        if array_name in self.entity_data:
+            if isinstance(self.entity_data[array_name], list):
+                value = self.entity_data[array_name][0]
+                self.entity_data[array_name] = self.entity_data[array_name][1:]
+                return value
+
+        return None
+
+    def reload(self):
+        if not self.can_mutate_or_load():
+            raise ValueError('Unable to reload entity: No uuid nor name')
+
+        response = Usergrid.GET(collection=self.entity_data.get('type'),
+                                uuid_name=self.entity_id())
+        if response.ok:
+            self.entity_data.update(response.entity().entity_data)
+
+        else:
+            raise ValueError('Unable to reload entity: %s' % response)
+
+    def save(self):
+        if not self.can_mutate_or_load():
+            raise ValueError('Unable to save entity: No uuid nor name')
+
+        response = Usergrid.PUT(collection=self.entity_data.get('type'),
+                                uuid_name=self.entity_id(),
+                                data=self.entity_data)
+
+        if response.ok and 'uuid' not in self.entity_data:
+            self.entity_data['uuid'] = response.entity().get('uuid')
+
+        return response
+
+    def remove(self):
+        if not self.can_mutate_or_load():
+            raise ValueError('Unable to delete entity: No uuid nor name')
+
+        return Usergrid.DELETE(collection=self.entity_data.get('type'),
+                               uuid_name=self.entity_id())
+
+    def get_connections(self, relationship, direction='connecting'):
+        pass
+
+    def connect(self, relationship, to_entity):
+
+        if not to_entity.can_mutate_or_load():
+            raise ValueError('Unable to connect to entity - no uuid or name')
+
+        if not self.can_mutate_or_load():
+            raise ValueError('Unable from connect to entity - no uuid or name')
+
+        return Usergrid.connect_entities(self, relationship, to_entity)
+
+    def disconnect(self, relationship, to_entity):
+        if not to_entity.can_mutate_or_load():
+            raise ValueError('Unable to connect to entity - no uuid or name')
+
+        if not self.can_mutate_or_load():
+            raise ValueError('Unable from connect to entity - no uuid or name')
+
+        return Usergrid.disconnect_entities(self, relationship, to_entity)
+
+    def attach_asset(self, filename, data, content_type):
+        pass
+
+    def download_asset(self, content_type=None):
+        pass
+
+
+class UsergridClient(object):
+    def __init__(self,
+                 org_id,
+                 app_id,
+                 base_url='http://api.usergrid.com',
+                 client_id=None,
+                 client_secret=None,
+                 token_ttl_seconds=86400,
+                 auth_fallback="none"):
+
+        self.base_url = base_url
+        self.org_id = org_id
+        self.app_id = app_id
+        self.auth_fallback = auth_fallback
+        self.logger = logging.getLogger('usergrid.UsergridClient')
+        self.session = requests.Session()
+
+        self.url_data = {
+            'base_url': base_url,
+            'org_id': org_id,
+            'app_id': app_id
+        }
+
+        if client_id and not client_secret:
+            value_error('Client ID Specified but not Secret')
+
+        elif client_secret and not client_id:
+            value_error('Client ID Specified but not Secret')
+
+        elif client_secret and client_id:
+            self.auth = UsergridAppAuth(client_id=client_id,
+                                        client_secret=client_secret,
+                                        token_ttl_seconds=token_ttl_seconds)
+
+            self.auth.authenticate(self)
+            self.session.headers.update({'Authorization': 'Bearer %s' % self.auth.access_token})
+
+    def __str__(self):
+        return json.dumps({
+            'base_url': self.base_url,
+            'org_id': self.org_id,
+            'app_id': self.app_id,
+            'access_token': self.auth.access_token
+        })
+
+    def GET(self, collection, uuid_name, connections='none', auth=None, **kwargs):
+        url = get_entity_url_template.format(collection=collection,
+                                             uuid_name=uuid_name,
+                                             connections=connections,
+                                             **self.url_data)
+        if auth:
+            r = requests.get(url, headers={'Authorization': 'Bearer %s' % auth.access_token})
+
+        else:
+            r = self.session.get(url)
+
+        return UsergridResponse(r, self)
+
+    def PUT(self, collection, uuid_name, data, auth=None, **kwargs):
+        url = put_entity_url_template.format(collection=collection,
+                                             uuid_name=uuid_name,
+                                             **self.url_data)
+
+        if auth:
+            r = requests.put(url,
+                             data=json.dumps(data),
+                             headers={'Authorization': 'Bearer %s' % auth.access_token})
+        else:
+            r = self.session.put(url, data=json.dumps(data))
+
+        return UsergridResponse(r, self)
+
+    def POST(self, collection, data, auth=None, **kwargs):
+        url = post_collection_url_template.format(collection=collection,
+                                                  **self.url_data)
+
+        if auth:
+            r = requests.post(url,
+                              data=json.dumps(data),
+                              headers={'Authorization': 'Bearer %s' % auth.access_token})
+        else:
+            r = self.session.post(url, data=json.dumps(data))
+
+        return UsergridResponse(r, self)
+
+    def DELETE(self, collection, uuid_name, auth=None, **kwargs):
+        url = delete_entity_url_template.format(collection=collection,
+                                                uuid_name=uuid_name,
+                                                **self.url_data)
+
+        if auth:
+            r = requests.delete(url, headers={'Authorization': 'Bearer %s' % auth.access_token})
+        else:
+            r = self.session.delete(url)
+
+        return UsergridResponse(r, self)
+
+    def connect_entities(self, from_entity, relationship, to_entity, auth=None, **kwargs):
+
+        url = connect_entities_by_type_template.format(from_collection=from_entity.get('type'),
+                                                       from_uuid_name=from_entity.entity_id(),
+                                                       relationship=relationship,
+                                                       to_collection=to_entity.get('type'),
+                                                       to_uuid_name=to_entity.entity_id(),
+                                                       **self.url_data)
+
+        if auth:
+            r = requests.post(url, headers={'Authorization': 'Bearer %s' % auth.access_token})
+        else:
+            r = self.session.post(url)
+
+        return UsergridResponse(r, self)
+
+    def assign_role(self, role_uuid_name, entity, auth=None, **kwargs):
+        url = assign_role_url_template.format(role_uuid_name=role_uuid_name,
+                                              entity_type=entity.get('type'),
+                                              entity_uuid_name=entity.entity_id(),
+                                              **self.url_data)
+
+        if auth:
+            r = requests.delete(url, headers={'Authorization': 'Bearer %s' % auth.access_token})
+        else:
+            r = self.session.delete(url)
+
+        return UsergridResponse(r, self)
+
+
+def disconnect_entities(self, from_entity, relationship, to_entity, auth=None, **kwargs):
+        url = connect_entities_by_type_template.format(from_collection=from_entity.get('type'),
+                                                       from_uuid_name=from_entity.entity_id(),
+                                                       relationship=relationship,
+                                                       to_collection=to_entity.get('type'),
+                                                       to_uuid_name=to_entity.entity_id(),
+                                                       **self.url_data)
+
+        if auth:
+            r = requests.delete(url, headers={'Authorization': 'Bearer %s' % auth.access_token})
+        else:
+            r = self.session.delete(url)
+
+        return UsergridResponse(r, self)
+
+
+class UsergridUser(object):
+    def __init__(self):
+        pass
+
+
+class UsergridAsset(object):
+    def __init__(self, filename, data, content_type):
+        self.filename = filename
+        self.data = data
+        self.content_type = content_type

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/usergrid/UsergridCollection.py
----------------------------------------------------------------------
diff --git a/sdks/python/usergrid/UsergridCollection.py b/sdks/python/usergrid/UsergridCollection.py
new file mode 100644
index 0000000..a37ad95
--- /dev/null
+++ b/sdks/python/usergrid/UsergridCollection.py
@@ -0,0 +1,77 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+class UsergridCollection(object):
+    def __init__(self, org_id, app_id, collection_name, client):
+        self.org_id = org_id
+        self.app_id = app_id
+        self.collection_name = collection_name
+        self.client = client
+
+    def __str__(self):
+        return json.dumps({
+            'org_id': self.org_id,
+            'app_id': self.app_id,
+            'collection_name': self.collection_name,
+        })
+
+    def entity(self, uuid):
+        pass
+
+    def entity_from_data(self, data):
+        return UsergridEntity(org_id=self.org_id,
+                              app_id=self.app_id,
+                              collection_name=self.collection_name,
+                              data=data,
+                              client=self.client)
+
+    def query(self, ql='select *', limit=100):
+        url = collection_query_url_template.format(app_id=self.app_id,
+                                                   ql=ql,
+                                                   limit=limit,
+                                                   collection=self.collection_name,
+                                                   **self.client.url_data)
+
+        return UsergridQuery(url, headers=self.client.headers)
+
+    def entities(self, **kwargs):
+        return self.query(**kwargs)
+
+    def post(self, entity, **kwargs):
+        url = collection_url_template.format(collection=self.collection_name,
+                                             app_id=self.app_id,
+                                             **self.client.url_data)
+
+        r = self.client.post(url, data=entity, **kwargs)
+
+        if r.status_code == 200:
+            api_response = r.json()
+            entity = api_response.get('entities')[0]
+            e = UsergridEntity(org_id=self.org_id,
+                               app_id=self.app_id,
+                               collection_name=self.collection_name,
+                               data=entity,
+                               client=self.client)
+            return e
+
+        else:
+            raise UsergridError(message='Unable to post to collection name=[%s]' % self.collection_name,
+                                status_code=r.status_code,
+                                data=entity,
+                                api_response=r,
+                                url=url)
+

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/usergrid/UsergridConnection.py
----------------------------------------------------------------------
diff --git a/sdks/python/usergrid/UsergridConnection.py b/sdks/python/usergrid/UsergridConnection.py
new file mode 100644
index 0000000..82d3fdc
--- /dev/null
+++ b/sdks/python/usergrid/UsergridConnection.py
@@ -0,0 +1,26 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+import logging
+
+
+class UsergridConnection(object):
+    def __init__(self, source_entity, verb, target_entity):
+        self.source_entity = source_entity
+        self.verb = verb
+        self.target_entity = target_entity
+        self.logger = logging.getLogger('usergrid.UsergridConnection')

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/usergrid/UsergridError.py
----------------------------------------------------------------------
diff --git a/sdks/python/usergrid/UsergridError.py b/sdks/python/usergrid/UsergridError.py
new file mode 100644
index 0000000..a5cf0bb
--- /dev/null
+++ b/sdks/python/usergrid/UsergridError.py
@@ -0,0 +1,17 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/usergrid/UsergridOrganization.py
----------------------------------------------------------------------
diff --git a/sdks/python/usergrid/UsergridOrganization.py b/sdks/python/usergrid/UsergridOrganization.py
new file mode 100644
index 0000000..c0d345b
--- /dev/null
+++ b/sdks/python/usergrid/UsergridOrganization.py
@@ -0,0 +1,31 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+from usergrid import UsergridApplication
+
+
+class UsergridOrganization(object):
+    def __init__(self, org_id, client):
+        self.org_id = org_id
+        self.client = client
+
+    def application(self, app_id):
+        return UsergridApplication(app_id, client=self.client)
+
+    def app(self, app_id):
+        return self.application(app_id)
+

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/usergrid/UsergridQueryIterator.py
----------------------------------------------------------------------
diff --git a/sdks/python/usergrid/UsergridQueryIterator.py b/sdks/python/usergrid/UsergridQueryIterator.py
new file mode 100755
index 0000000..301ea7d
--- /dev/null
+++ b/sdks/python/usergrid/UsergridQueryIterator.py
@@ -0,0 +1,155 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+import json
+import logging
+import traceback
+import requests
+import time
+
+__author__ = 'Jeff West @ ApigeeCorporation'
+
+
+class UsergridQueryIterator(object):
+    def __init__(self,
+                 url,
+                 operation='GET',
+                 sleep_time=10,
+                 page_delay=0,
+                 headers=None,
+                 data=None):
+
+        if not data:
+            data = {}
+        if not headers:
+            headers = {}
+
+        self.page_counter = 0
+        self.total_retrieved = 0
+        self.logger = logging.getLogger('usergrid.UsergridQuery')
+        self.data = data
+        self.headers = headers
+        self.url = url
+        self.operation = operation
+        self.next_cursor = None
+        self.entities = []
+        self.count_retrieved = 0
+        self._pos = 0
+        self.last_response = None
+        self.page_delay = page_delay
+        self.sleep_time = sleep_time
+        self.session = None
+
+    def _get_next_response(self, attempts=0):
+
+        if self.session is None:
+            self.session = requests.Session()
+
+        try:
+            if self.operation == 'PUT':
+                op = self.session.put
+            elif self.operation == 'DELETE':
+                op = self.session.delete
+            else:
+                op = self.session.get
+
+            target_url = self.url
+
+            if self.next_cursor is not None:
+                delim = '&' if '?' in target_url else '?'
+                target_url = '%s%scursor=%s' % (self.url, delim, self.next_cursor)
+
+            self.logger.debug('Operation=[%s] URL=[%s]' % (self.operation, target_url))
+
+            r = op(target_url, data=json.dumps(self.data), headers=self.headers)
+
+            if r.status_code == 200:
+                r_json = r.json()
+                count_retrieved = len(r_json.get('entities', []))
+                self.total_retrieved += count_retrieved
+                self.logger.debug('Retrieved [%s] entities in [%s]th page in [%s], total from [%s] is [%s]' % (
+                    count_retrieved, self.page_counter, r.elapsed, self.url, self.total_retrieved))
+
+                return r_json
+
+            elif r.status_code in [401, 404] and 'service_resource_not_found' in r.text:
+                self.logger.error('Query Not Found [%s] on URL=[%s]: %s' % (r.status_code, target_url, r.text))
+                raise SystemError('Query Not Found [%s] on URL=[%s]: %s' % (r.status_code, target_url, r.text))
+
+            else:
+                if attempts < 10:
+                    self.logger.error('Sleeping %s after HTTP [%s] for retry attempt=[%s] on URL=[%s], response: %s' % (
+                        self.sleep_time, r.status_code, attempts, target_url, r.text))
+
+                    time.sleep(self.sleep_time)
+
+                    return self._get_next_response(attempts=attempts + 1)
+
+                else:
+                    raise SystemError('Unable to get next response after %s attempts' % attempts)
+
+        except:
+            print traceback.format_exc()
+
+    def next(self):
+
+        if self.last_response is None:
+            self.logger.debug('getting first page, url=[%s]' % self.url)
+
+            self._process_next_page()
+
+        elif self._pos >= len(self.entities) > 0 and self.next_cursor is not None:
+
+            self.logger.debug('getting next page, count=[%s] url=[%s], cursor=[%s]' % (
+                self.count_retrieved, self.url, self.next_cursor))
+
+            self._process_next_page()
+            self.logger.debug('Sleeping [%s]s between pages' % self.page_delay)
+
+            time.sleep(self.page_delay)
+
+        if self._pos < len(self.entities):
+            response = self.entities[self._pos]
+            self._pos += 1
+            return response
+
+        raise StopIteration
+
+    def __iter__(self):
+        return self
+
+    def _process_next_page(self, attempts=0):
+
+        api_response = self._get_next_response()
+
+        if api_response is None:
+            message = 'Unable to retrieve query results from url=[%s]' % self.url
+            self.logger.error(message)
+            api_response = {}
+            raise StopIteration
+
+        self.last_response = api_response
+
+        self.entities = api_response.get('entities', [])
+        self.next_cursor = api_response.get('cursor', None)
+        self._pos = 0
+        self.count_retrieved += len(self.entities)
+        self.page_counter += 1
+
+        if self.next_cursor is None:
+            self.logger.debug('no cursor in response. Total pages=[%s], entities=[%s] url=[%s]' % (
+                self.page_counter, self.count_retrieved, self.url))

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/usergrid/__init__.py
----------------------------------------------------------------------
diff --git a/sdks/python/usergrid/__init__.py b/sdks/python/usergrid/__init__.py
new file mode 100644
index 0000000..93f8273
--- /dev/null
+++ b/sdks/python/usergrid/__init__.py
@@ -0,0 +1,37 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+# 
+#      http://www.apache.org/licenses/LICENSE-2.0
+# 
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+__all__ = [
+    'UsergridApplication',
+    'UsergridClient',
+    'UsergridConnection',
+    'UsergridConnectionProfile',
+    'UsergridEntity',
+    'Usergrid',
+    'UsergridError',
+    'UsergridOrganization',
+    'UsergridAuth',
+    'UsergridQueryIterator',
+    'UsergridResponse'
+]
+
+from .UsergridApplication import UsergridApplication
+from .UsergridClient import UsergridClient, Usergrid, UsergridResponse
+from .UsergridConnection import UsergridConnection
+from .UsergridOrganization import UsergridOrganization
+from .UsergridQueryIterator import UsergridQueryIterator
+from .UsergridAuth import UsergridAuth

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/usergrid/app_templates.py
----------------------------------------------------------------------
diff --git a/sdks/python/usergrid/app_templates.py b/sdks/python/usergrid/app_templates.py
new file mode 100644
index 0000000..cb953ac
--- /dev/null
+++ b/sdks/python/usergrid/app_templates.py
@@ -0,0 +1,36 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+__author__ = 'Jeff West @ ApigeeCorporation'
+
+org_url_template = "{base_url}/{org_id}"
+app_url_template = "%s/{app_id}" % org_url_template
+
+app_token_url_template = "%s/token" % app_url_template
+
+collection_url_template = "%s/{collection}" % app_url_template
+collection_query_url_template = "%s?ql={ql}&limit={limit}" % collection_url_template
+
+post_collection_url_template = collection_url_template
+entity_url_template = "%s/{uuid_name}" % collection_url_template
+get_entity_url_template = "%s?connections={connections}" % entity_url_template
+put_entity_url_template = entity_url_template
+delete_entity_url_template = entity_url_template
+
+assign_role_url_template = '%s/roles/{role_uuid_name}/{entity_type}/{entity_uuid_name}' % app_url_template
+
+connect_entities_by_type_template = '%s/{from_collection}/{from_uuid_name}/{relationship}/{to_collection}/{to_uuid_name}' % app_url_template
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/sdks/python/usergrid/management_templates.py
----------------------------------------------------------------------
diff --git a/sdks/python/usergrid/management_templates.py b/sdks/python/usergrid/management_templates.py
new file mode 100644
index 0000000..62c531c
--- /dev/null
+++ b/sdks/python/usergrid/management_templates.py
@@ -0,0 +1,25 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  The ASF licenses this file to You
+#  under the Apache License, Version 2.0 (the "License"); you may not
+#  use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.  For additional information regarding
+#  copyright in this work, please see the NOTICE file in the top level
+#  directory of this distribution.
+
+__author__ = 'Jeff West @ ApigeeCorporation'
+
+management_base_url = '{base_url}/management'
+management_org_url_template = "%s/organizations/{org_id}" % management_base_url
+management_org_list_apps_url_template = "%s/applications" % management_org_url_template
+management_app_url_template = "%s/applications/{app_id}" % management_org_url_template
+
+org_token_url_template = "%s/token" % management_base_url

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/utils/usergrid-util-python/.gitignore
----------------------------------------------------------------------
diff --git a/utils/usergrid-util-python/.gitignore b/utils/usergrid-util-python/.gitignore
new file mode 100644
index 0000000..a6e3315
--- /dev/null
+++ b/utils/usergrid-util-python/.gitignore
@@ -0,0 +1,61 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# custom
+sandbox
+0e4b82c5-9aad-45de-810a-ff07c281ed2d_1454177649_export.zip

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/utils/usergrid-util-python/LICENSE
----------------------------------------------------------------------
diff --git a/utils/usergrid-util-python/LICENSE b/utils/usergrid-util-python/LICENSE
new file mode 100644
index 0000000..4db993e
--- /dev/null
+++ b/utils/usergrid-util-python/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Jeffrey West
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/utils/usergrid-util-python/README.md
----------------------------------------------------------------------
diff --git a/utils/usergrid-util-python/README.md b/utils/usergrid-util-python/README.md
new file mode 100644
index 0000000..7d3b533
--- /dev/null
+++ b/utils/usergrid-util-python/README.md
@@ -0,0 +1,15 @@
+# Usergrid Tools (in Python)
+
+## Prerequisites
+
+* Install the Usergrid Python SDK: `pip install usergrid`
+* Install Usergrid Tools: `pip install usergrid-tools`
+
+
+## Overview
+The purpose of this module is to provide tools for working with Usergrid.  The tools included as console scripts are:
+* `usergrid_data_migrator` - [README](https://github.com/jwest-apigee/usergrid-util-python/blob/master/usergrid_tools/migration/README.md) A tool for migrating data from one Usergrid installation to another (or org1->org2)
+* `parse_importer` - [README](https://github.com/jwest-apigee/usergrid-util-python/blob/master/usergrid_tools/parse_importer/README.md) A tool for importing data from a Parse.com data export into Usergrid
+* `index_test` -  [README](https://github.com/jwest-apigee/usergrid-util-python/blob/master/usergrid_tools/indexing/README.md) A tool for testing indexing latency in Usergrid
+
+For information on those tools, please see the respective README files

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/utils/usergrid-util-python/es_tools/alias_mover.py
----------------------------------------------------------------------
diff --git a/utils/usergrid-util-python/es_tools/alias_mover.py b/utils/usergrid-util-python/es_tools/alias_mover.py
new file mode 100644
index 0000000..2a8fe02
--- /dev/null
+++ b/utils/usergrid-util-python/es_tools/alias_mover.py
@@ -0,0 +1,129 @@
+import json
+
+import requests
+
+example_request = {
+    "actions": [
+        {
+            "remove": {
+                "index": "apigee-vfmplus",
+                "alias": "rug000sr_euwi_1edb82a0-f23c-11e5-bf51-0aa04517d9d9_read_alias"
+            }
+        },
+        {
+            "remove": {
+                "index": "apigee-vfmplus",
+                "alias": "rug000sr_euwi_1edb82a0-f23c-11e5-bf51-0aa04517d9d9_write_alias"
+            }
+        },
+        {
+            "remove": {
+                "index": "apigee-vfmplus",
+                "alias": "rug000sr_euwi_48e5394a-f1fd-11e5-9fdc-06ae5d93d39b_read_alias"
+            }
+        },
+        {
+            "remove": {
+                "index": "apigee-vfmplus",
+                "alias": "rug000sr_euwi_48e5394a-f1fd-11e5-9fdc-06ae5d93d39b_write_alias"
+            }
+        },
+        {
+            "remove": {
+                "index": "apigee-vfmplus",
+                "alias": "rug000sr_euwi_fd7ef86f-f1fb-11e5-b407-02f0703cf0bf_read_alias"
+            }
+        },
+        {
+            "remove": {
+                "index": "apigee-vfmplus",
+                "alias": "rug000sr_euwi_fd7ef86f-f1fb-11e5-b407-02f0703cf0bf_write_alias"
+            }
+        },
+        {
+            "add": {
+                "index": "apigee-vmplus-docvalues",
+                "alias": "rug000sr_euwi_1edb82a0-f23c-11e5-bf51-0aa04517d9d9_read_alias"
+            }
+        },
+        {
+            "add": {
+                "index": "apigee-vmplus-docvalues",
+                "alias": "rug000sr_euwi_1edb82a0-f23c-11e5-bf51-0aa04517d9d9_write_alias"
+            }
+        },
+        {
+            "add": {
+                "index": "apigee-vmplus-docvalues",
+                "alias": "rug000sr_euwi_48e5394a-f1fd-11e5-9fdc-06ae5d93d39b_read_alias"
+            }
+        },
+        {
+            "add": {
+                "index": "apigee-vmplus-docvalues",
+                "alias": "rug000sr_euwi_48e5394a-f1fd-11e5-9fdc-06ae5d93d39b_write_alias"
+            }
+        },
+        {
+            "add": {
+                "index": "apigee-vmplus-docvalues",
+                "alias": "rug000sr_euwi_fd7ef86f-f1fb-11e5-b407-02f0703cf0bf_read_alias"
+            }
+        },
+        {
+            "add": {
+                "index": "apigee-vmplus-docvalues",
+                "alias": "rug000sr_euwi_fd7ef86f-f1fb-11e5-b407-02f0703cf0bf_write_alias"
+            }
+        }
+    ]
+}
+
+cluster = 'rug000sr_euwi'
+
+work = {
+    # 'remove': {
+    #     '2dd3bf6c-02a5-11e6-8623-069e4448b365': 'rug000sr_euwi_applications_3',
+    #     '333af5b3-02a5-11e6-81cb-02fe3195fdff': 'rug000sr_euwi_applications_3',
+    # },
+    'add': {
+        '2dd3bf6c-02a5-11e6-8623-069e4448b365': 'apigee-vfmplus-1-no-doc-18',
+        '333af5b3-02a5-11e6-81cb-02fe3195fdff': 'apigee-vfmplus-1-no-doc-18',
+    }
+}
+
+actions = []
+
+for app_id, index in work.get('remove', {}).iteritems():
+    actions.append({
+        "remove": {
+            "index": index,
+            "alias": "%s_%s_read_alias" % (cluster, app_id)
+        },
+    })
+    actions.append({
+        "remove": {
+            "index": index,
+            "alias": "%s_%s_write_alias" % (cluster, app_id)
+        },
+    })
+
+for app_id, index in work['add'].iteritems():
+    actions.append({
+        "add": {
+            "index": index,
+            "alias": "%s_%s_read_alias" % (cluster, app_id)
+        },
+    })
+    actions.append({
+        "add": {
+            "index": index,
+            "alias": "%s_%s_write_alias" % (cluster, app_id)
+        },
+    })
+
+url = 'http://localhost:9200/_aliases'
+
+r = requests.post(url, data=json.dumps({'actions': actions}))
+
+print '%s: %s' % (r.status_code, r.text)

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/utils/usergrid-util-python/es_tools/cluster_shard_allocation.py
----------------------------------------------------------------------
diff --git a/utils/usergrid-util-python/es_tools/cluster_shard_allocation.py b/utils/usergrid-util-python/es_tools/cluster_shard_allocation.py
new file mode 100644
index 0000000..a462124
--- /dev/null
+++ b/utils/usergrid-util-python/es_tools/cluster_shard_allocation.py
@@ -0,0 +1,89 @@
+import json
+import time
+import requests
+
+__author__ = 'Jeff West @ ApigeeCorporation'
+
+# The purpose of this script is to set certain nodes in an ElasticSearch cluster to be excluded from index allocation,
+# generally for the purpose of shutting down or restarting the node
+
+SHUTDOWN_NODES = True
+
+nodes = [
+    # 'res206wo',
+    # 'res207wo',
+]
+
+base_url = 'http://localhost:9200'
+
+exclude_nodes = nodes
+
+nodes_string = ",".join(exclude_nodes)
+
+print 'Excluding: ' + nodes_string
+url_template = '%s/_cluster/settings' % base_url
+
+status_code = 503
+
+while status_code >= 500:
+    r = requests.put(
+        '%s/_cluster/settings' % base_url,
+        data=json.dumps({
+            "transient": {
+                "cluster.routing.allocation.exclude._host": nodes_string
+            }
+        }))
+
+    status_code = r.status_code
+
+    print '%s: %s' % (r.status_code, r.text)
+
+ready = False
+
+nodes_shut_down = []
+
+while not ready:
+
+    ready = True
+    nodes_left = 0
+    bytes_left = 0
+
+    for node in exclude_nodes:
+        node_url = '%s/_nodes/%s/stats' % (base_url, node)
+        r = requests.get(node_url)
+
+        if r.status_code == 200:
+            # print r.text
+
+            node_stats = r.json()
+
+            for field, data in node_stats.get('nodes').iteritems():
+                if data.get('name') == node:
+                    size = data.get('indices', {}).get('store', {}).get('size_in_bytes', 1)
+                    docs = data.get('indices', {}).get('docs', {}).get('count', 1)
+
+                    if size > 0 and docs > 0:
+                        print 'Node: %s - size %s' % (node, size)
+                        bytes_left += size
+                        ready = False and ready
+                        nodes_left += 1
+                    else:
+                        if SHUTDOWN_NODES:
+                            if not node in nodes_shut_down:
+                                nodes_shut_down.append(node)
+                                shutdown_url = '%s/_cluster/nodes/%s/_shutdown' % (base_url, node)
+
+                                print 'Shutting down node %s: %s' % (node, shutdown_url)
+
+                                r = requests.post(shutdown_url)
+
+                                if r.status_code == 200:
+                                    nodes_shut_down.append(node)
+                                    print 'Shut down node %s' % node
+                                else:
+                                    print 'Shutdown failed: %s: %s' % (r.status_code, r.text)
+    if not ready:
+        print 'NOT READY! Waiting for %s nodes and %s GB' % (nodes_left, bytes_left / 1024.0 / 1000000)
+        time.sleep(10)
+
+# print 'READY TO MOVE!'

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/utils/usergrid-util-python/es_tools/command_sender.py
----------------------------------------------------------------------
diff --git a/utils/usergrid-util-python/es_tools/command_sender.py b/utils/usergrid-util-python/es_tools/command_sender.py
new file mode 100644
index 0000000..92ecfff
--- /dev/null
+++ b/utils/usergrid-util-python/es_tools/command_sender.py
@@ -0,0 +1,42 @@
+import json
+import requests
+
+__author__ = 'Jeff West @ ApigeeCorporation'
+
+
+# Simple utility to send commands, useful to not have to recall the proper format
+
+#
+# url = 'http://localhost:9200/_cat/shards'
+#
+# r = requests.get(url)
+#
+# response = r.text
+#
+# print response
+
+data = {
+    "commands": [
+        {
+            "move": {
+                "index": "usergrid__a34ad389-b626-11e4-848f-06b49118d7d0__application_target_final",
+                "shard": 14,
+                "from_node": "res018sy",
+                "to_node": "res021sy"
+            }
+        },
+        {
+            "move": {
+                "index": "usergrid__a34ad389-b626-11e4-848f-06b49118d7d0__application_target_final",
+                "shard": 12,
+                "from_node": "res018sy",
+                "to_node": "res009sy"
+            }
+        },
+
+    ]
+}
+
+r = requests.post('http://localhost:9211/_cluster/reroute', data=json.dumps(data))
+
+print r.text
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/utils/usergrid-util-python/es_tools/es_index_iterator_reindexer.py
----------------------------------------------------------------------
diff --git a/utils/usergrid-util-python/es_tools/es_index_iterator_reindexer.py b/utils/usergrid-util-python/es_tools/es_index_iterator_reindexer.py
new file mode 100644
index 0000000..f151fcb
--- /dev/null
+++ b/utils/usergrid-util-python/es_tools/es_index_iterator_reindexer.py
@@ -0,0 +1,107 @@
+import json
+import re
+from multiprocessing.pool import Pool
+import requests
+
+# This script iterates an index and issues a PUT request for an empty string to force a reindex of the entity
+
+index_url_template = 'http://res013wo:9200/{index_name}/_search?size={size}&from={from_var}'
+
+index_names = [
+    'es-index-name'
+]
+
+baas_url = 'http://localhost:8080/org/{app_id}/{collection}/{entity_id}'
+
+counter = 0
+size = 1000
+
+total_docs = 167501577
+from_var = 0
+page = 0
+
+work_items = []
+
+
+def work(item):
+    url = 'http://localhost:8080/org/{app_id}/{collection}/{entity_id}'.format(
+        app_id=item[0],
+        collection=item[1],
+        entity_id=item[2]
+    )
+
+    r_put = requests.put(url, data=json.dumps({'russo': ''}))
+
+    if r_put.status_code == 200:
+        print '[%s]: %s' % (r_put.status_code, url)
+
+    elif r_put.status_code:
+        print '[%s]: %s | %s' % (r_put.status_code, url, r.text)
+
+
+while from_var < total_docs:
+
+    from_var = page * size
+    page += 1
+
+    for index_name in index_names:
+
+        index_url = index_url_template.format(index_name=index_name, size=size, from_var=from_var)
+
+        print 'Getting URL: ' + index_url
+
+        r = requests.get(index_url)
+
+        if r.status_code != 200:
+            print r.text
+            exit()
+
+        response = r.json()
+
+        hits = response.get('hits', {}).get('hits')
+
+        re_app_id = re.compile('appId\((.+),')
+        re_ent_id = re.compile('entityId\((.+),')
+        re_type = re.compile('entityId\(.+,(.+)\)')
+
+        print 'Index: %s | hits: %s' % (index_name, len(hits))
+
+        for hit_data in hits:
+            source = hit_data.get('_source')
+
+            application_id = source.get('applicationId')
+
+            app_id_find = re_app_id.findall(application_id)
+
+            if len(app_id_find) > 0:
+                app_id = app_id_find[0]
+
+                if app_id != '5f20f423-f2a8-11e4-a478-12a5923b55dc':
+                    continue
+
+                entity_id_tmp = source.get('entityId')
+
+                entity_id_find = re_ent_id.findall(entity_id_tmp)
+                entity_type_find = re_type.findall(entity_id_tmp)
+
+                if len(entity_id_find) > 0 and len(entity_type_find) > 0:
+                    entity_id = entity_id_find[0]
+                    collection = entity_type_find[0]
+
+                    if collection in ['logs', 'log']:
+                        print 'skipping logs...'
+                        continue
+
+                    work_items.append((app_id, collection, entity_id))
+
+                    counter += 1
+
+pool = Pool(16)
+
+print 'Work Items: %s' % len(work_items)
+
+print 'Starting Work'
+
+pool.map(work, work_items)
+
+print 'done: %s' % counter

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/utils/usergrid-util-python/es_tools/es_searcher.py
----------------------------------------------------------------------
diff --git a/utils/usergrid-util-python/es_tools/es_searcher.py b/utils/usergrid-util-python/es_tools/es_searcher.py
new file mode 100644
index 0000000..55e54ef
--- /dev/null
+++ b/utils/usergrid-util-python/es_tools/es_searcher.py
@@ -0,0 +1,24 @@
+import json
+import requests
+
+# Simple example of searching for a specific entity in ES
+
+__author__ = 'Jeff West @ ApigeeCorporation'
+
+url_template = 'http://localhost:9200/pea000ug_applications_2/_search'
+
+request = {
+    "query": {
+        "term": {
+            "entityId": "entityId(1a78d0a6-bffb-11e5-bc61-0af922a4f655,constratus)"
+        }
+    }
+}
+
+# url_template = 'http://localhost:9200/_search'
+# r = requests.get(url)
+r = requests.get(url_template, data=json.dumps(request))
+
+print r.status_code
+print json.dumps(r.json(), indent=2)
+

http://git-wip-us.apache.org/repos/asf/usergrid/blob/32f9e55d/utils/usergrid-util-python/es_tools/index_deleter.py
----------------------------------------------------------------------
diff --git a/utils/usergrid-util-python/es_tools/index_deleter.py b/utils/usergrid-util-python/es_tools/index_deleter.py
new file mode 100644
index 0000000..a697cf8
--- /dev/null
+++ b/utils/usergrid-util-python/es_tools/index_deleter.py
@@ -0,0 +1,68 @@
+import requests
+import logging
+
+__author__ = 'Jeff West @ ApigeeCorporation'
+
+
+# utility for deleting indexes that are no longer needed.  Given:
+# A) a set of strings to include when evaluating the index names to delete
+# B) a set of strings to Exclude when evaluating the index names to delete
+#
+# The general logic is:
+# 1) If the include set is empty, or if the index name contains a string in the 'include' set, then delete
+# 2) If the index contains a string in the exclude list, do not delete
+
+url_base = 'http://localhost:9200'
+
+r = requests.get(url_base + "/_stats")
+
+indices = r.json()['indices']
+
+print 'retrieved %s indices' % len(indices)
+
+NUMBER_VALUE = 0
+
+includes = [
+    'rug002sr_euwi',
+    # 'rug002mr',
+    # 'b6768a08-b5d5-11e3-a495-10ddb1de66c3',
+    # 'b6768a08-b5d5-11e3-a495-11ddb1de66c9',
+]
+
+excludes = [
+    # 'b6768a08-b5d5-11e3-a495-11ddb1de66c8',
+    # 'b6768a08-b5d5-11e3-a495-10ddb1de66c3',
+    # 'b6768a08-b5d5-11e3-a495-11ddb1de66c9',
+    # 'a34ad389-b626-11e4-848f-06b49118d7d0'
+]
+
+counter = 0
+process = False
+delete_counter = 0
+
+for index in indices:
+    process = False
+    counter += 1
+
+    print 'index %s of %s' % (counter, len(indices))
+
+    if len(includes) == 0:
+        process = True
+    else:
+        for include in includes:
+
+            if include in index:
+                process = True
+
+    if len(excludes) > 0:
+        for exclude in excludes:
+            if exclude in index:
+                process = False
+
+    if process:
+        delete_counter += 1
+
+        url_template = '%s/%s' % (url_base, index)
+        print 'DELETING Index [%s] %s at URL %s' % (delete_counter, index, url_template)
+
+        response = requests.delete('%s/%s' % (url_base, index))