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))