Skip to content
Browse files

Initial commit.

  • Loading branch information...
0 parents commit f8b2bda0e0bc5f24dc5fe5f84551490808af5592 @paddyforan paddyforan committed Apr 30, 2012
Showing with 307 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +8 −0 LICENSE.txt
  3. +1 −0 MANIFEST.in
  4. +29 −0 README.md
  5. +209 −0 iron_core.py
  6. +33 −0 setup.py
  7. +25 −0 test.py
2 .gitignore
@@ -0,0 +1,2 @@
+*.pyc
+*.swp
8 LICENSE.txt
@@ -0,0 +1,8 @@
+Copyright (c) 2012, Iron.io, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1 MANIFEST.in
@@ -0,0 +1 @@
+include LICENSE.txt
29 README.md
@@ -0,0 +1,29 @@
+# iron_core_python
+
+iron_core_python is a collection of common functions for working with the RESTful APIs
+that we build at [Iron.io](http://www.iron.io) from Python.
+
+## It Is
+
+* Service-agnostic
+* Pip- and easy_install-installable
+* Well documented
+
+## It Is Not
+
+* An API wrapper. Those are specific to each service, and you can generally find them
+by checking the documentation for the service.
+* A place for service-specific code. This is only meant to handle the basic, common
+interaction.
+
+## Installation
+
+You can use [pip](http://pip-installer.org) or [easy_install](http://wiki.python.org/moin/EasyInstall)
+to install the [release version](http://pypi.python.org/pypi/iron_core_python). If you'd
+like to work with a development or beta version, retrieve the files [from Github](https://github.com/iron-io/iron_core_python)
+and run `python setup.py install` from the root directory.
+
+## License
+
+This software is released under the BSD 2-Clause License. You can find the full text of
+this license under LICENSE.txt in the module's root directory.
209 iron_core.py
@@ -0,0 +1,209 @@
+import httplib
+import time
+
+
+class TooManyRetriesError(Exception):
+ def __str__(self):
+ return repr("Max retries reached. Aborting.")
+
+
+class ServiceUnavailable(Exception):
+ def __str__(self):
+ return repr("503 error from server.")
+
+
+class IronClient:
+ def __init__(self, name, version, host, project_id, token, protocol, port,
+ api_version):
+ """Prepare a Client that can make HTTP calls and return it.
+
+ Keyword arguments:
+ name -- the name of the client. Required.
+ version -- the version of the client. Required.
+ host -- the default domain the client will be requesting. Required.
+ project_id -- the project ID the client will be requesting. Can be
+ found on http://hud.iron.io. Required.
+ token -- an API token found on http://hud.iron.io. Required.
+ protocol -- The default protocol the client will use for its requests.
+ Required.
+ port -- The default port the client will use for its requests. Required.
+ api_version -- The version of the API the client will use for its
+ requests. Required.
+ """
+ self.name = name
+ self.version = version
+ self.host = host
+ self.project_id = project_id
+ self.token = token
+ self.protocol = protocol
+ self.port = int(port)
+ self.api_version = api_version
+ self.headers = {
+ "Accept": "application/json",
+ "User-Agent": "%s (version: %s)" % (self.name, self.version)
+ }
+ if self.token:
+ self.headers["Authorization"] = "OAuth %s" % self.token
+ self.base_url = "%s://%s:%s/%s/" % (self.protocol, self.host,
+ self.port, self.api_version)
+ if self.project_id:
+ self.base_url += "projects/%s/" % self.project_id
+ if self.protocol == "https" and self.port != httplib.HTTPS_PORT:
+ raise ValueError("Invalid port (%s) for an HTTPS request. Want %s."
+ % (self.port, httplib.HTTPS_PORT))
+
+ @classmethod
+ def retry(f, exceptionToCheck, tries=5, delay=.5, backoff=2, logger=None,
+ exceptionToRaise=TooManyRetriesError):
+ """A decorator to implement exponential backoff in other functions.
+
+ Keyword arguments:
+ f -- The function. Automatically supplied when retry is a decorator.
+ Required.
+ exceptionToCheck -- The exception class that should trigger a retry.
+ tries -- The maximum number of times to try. Defaults to 5.
+ delay -- The initial delay (in seconds) before retrying. Defaults
+ to .5.
+ backoff -- The number to multiply delay by after every failure.
+ Defaults to 2.
+ logger -- an instance of logging to log debug info to. Defaults
+ to None.
+ exceptionToRaise -- The exception class that should be raised after
+ tries has been reached. Defaults to
+ iron_core.TooManyRetriesError.
+ """
+ if backoff <= 1:
+ raise ValueError("backoff must be greater than 1")
+
+ if tries < 0:
+ raise ValueError("tries must be 0 or greater")
+
+ if delay <= 0:
+ raise ValueError("delay must be greater than 0")
+
+ def deco_retry(f):
+ def f_retry(*args, **kwargs):
+ # makes args modifiable
+ mtries = tries
+ mdelay = delay
+ done = False
+ rv = None
+ while mtries > 0 and not done:
+ try_msg = "Attempt #%s" % (tries - mtries + 1)
+ try:
+ if logger:
+ logger.debug(try_msg)
+ rv = f(*args, **kwargs)
+ done = True
+ except exceptionToCheck, e:
+ err_s = ""
+ if "%s" % e != "":
+ err_s = " (\"%s\")" % e
+ try_s = ""
+ if mtries > 1:
+ try_s = " Retrying after %s seconds." % mdelay
+ if logger:
+ logger.debug("Failed%s.%s", err_s, try_s)
+ rv = None
+ done = False
+ if mtries > 1:
+ time.sleep(mdelay)
+ mdelay *= backoff
+ mtries -= 1
+ if done:
+ if logger:
+ logger.debug("Success! Returning.")
+ return rv
+ raise exceptionToRaise
+ return f_retry
+ return deco_retry
+
+ def request(self, url, method, body="", headers={}):
+ """Execute an HTTP request and return a dict containing the response
+ and the response status code.
+
+ Keyword arguments:
+ url -- The path to execute the result against, not including the API
+ version or project ID, with no leading /. Required.
+ method -- The HTTP method to use. Required.
+ body -- A string or file object to send as the body of the request.
+ Defaults to an empty string.
+ headers -- HTTP Headers to send with the request. Can overwrite the
+ defaults. Defaults to {}.
+ """
+ if headers:
+ headers = dict(list(headers.items()) + list(self.headers.items()))
+ else:
+ headers = self.headers
+
+ if self.protocol == "http":
+ conn = httplib.HTTPConnection(self.host, self.port)
+ elif self.protocol == "https":
+ conn = httplib.HTTPSConnection(self.host, self.port)
+ else:
+ raise ValueError("Invalid protocol.")
+
+ url = self.base_url + url
+
+ conn.request(method, url, body, headers)
+ resp = conn.getresponse()
+ result = {}
+ result["body"] = resp.read()
+ result["status"] = resp.status
+ conn.close()
+ return result
+
+ def get(self, url, headers={}):
+ """Execute an HTTP GET request and return a dict containing the
+ response and the response status code.
+
+ Keyword arguments:
+ url -- The path to execute the result against, not including the API
+ version or project ID, with no leading /. Required.
+ headers -- HTTP Headers to send with the request. Can overwrite the
+ defaults. Defaults to {}.
+ """
+ return self.request(url=url, method="GET", headers=headers)
+
+ def post(self, url, body="", headers={}):
+ """Execute an HTTP POST request and return a dict containing the
+ response and the response status code.
+
+ Keyword arguments:
+ url -- The path to execute the result against, not including the API
+ version or project ID, with no leading /. Required.
+ body -- A string or file object to send as the body of the request.
+ Defaults to an empty string.
+ headers -- HTTP Headers to send with the request. Can overwrite the
+ defaults. Defaults to {}.
+ """
+ if headers == None:
+ headers = {}
+ headers["Content-Length"] = len(body)
+ return self.request(url=url, method="POST", body=body, headers=headers)
+
+ def delete(self, url, headers={}):
+ """Execute an HTTP DELETE request and return a dict containing the
+ response and the response status code.
+
+ Keyword arguments:
+ url -- The path to execute the result against, not including the API
+ version or project ID, with no leading /. Required.
+ headers -- HTTP Headers to send with the request. Can overwrite the
+ defaults. Defaults to an empty dict.
+ """
+ return self.request(url=url, method="DELETE", headers=headers)
+
+ def put(self, url, body="", headers={}):
+ """Execute an HTTP PUT request and return a dict containing the
+ response and the response status code.
+
+ Keyword arguments:
+ url -- The path to execute the result against, not including the API
+ version or project ID, with no leading /. Required.
+ body -- A string or file object to send as the body of the request.
+ Defaults to an empty string.
+ headers -- HTTP Headers to send with the request. Can overwrite the
+ defaults. Defaults to {}.
+ """
+ return self.request(url=url, method="PUT", body=body, headers=headers)
33 setup.py
@@ -0,0 +1,33 @@
+from distutils.core import setup
+
+setup(
+ name = "IronCore",
+ py_modules = ["iron_core"],
+ version = "0.1.0",
+ description = "Universal classes and methods for Iron.io API wrappers to build on.",
+ author = "Iron.io",
+ author_email = "thirdparty@iron.io",
+ url = "https://www.github.com/iron-io/iron_core_python",
+ keywords = ["Iron.io"],
+ classifiers = [
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Intended Audience :: Developers",
+ "Operating System :: OS Independent",
+ "Development Status :: 2 - Pre-Alpha",
+ "License :: OSI Approved :: BSD License",
+ "Natural Language :: English",
+ "Topic :: Internet",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+
+ ],
+ long_description = """\
+Iron.io common library
+----------------------
+
+This package offers common functions for Iron.io APIs and services. It does not wrap
+any APIs or contain API-specific features, but serves as a common base that wrappers
+may be built on. Users looking for API wrappers should instead look at
+iron_worker_python and iron_worker_mq."""
+)
25 test.py
@@ -0,0 +1,25 @@
+import iron_rest
+import logging
+
+logger = logging.getLogger("iron_rest_test")
+logger.setLevel(logging.DEBUG)
+ch = logging.StreamHandler()
+ch.setLevel(logging.DEBUG)
+logger.addHandler(ch)
+
+@iron_rest.IronClient.retry(Exception)
+def success_retry_test():
+ print "SUCCESS from success_retry_test!"
+
+@iron_rest.IronClient.retry(Exception, logger=logger)
+def fail_retry_test():
+ print "FAIL from fail_retry_test :("
+ raise Exception()
+
+@iron_rest.IronClient.retry(Exception, logger=logger)
+def return_retry_test(msg):
+ return msg
+
+success_retry_test()
+print return_retry_test("Return from return_retry_test")
+fail_retry_test()

0 comments on commit f8b2bda

Please sign in to comment.
Something went wrong with that request. Please try again.