diff --git a/README.md b/README.md index ed7b40b..a7bbbf5 100644 --- a/README.md +++ b/README.md @@ -6,21 +6,20 @@ that we build at [Iron.io](http://www.iron.io) from Python. ## It Is * Service-agnostic -* Pip- and easy_install-installable +* Pip-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. + by checking the documentation for the service. * A place for service-specific code. This is only meant to handle the basic, common -interaction. + 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) +You can use [pip](http://pip-installer.org) 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 diff --git a/iron_core.py b/iron_core.py index 085df33..022c3a0 100644 --- a/iron_core.py +++ b/iron_core.py @@ -1,21 +1,14 @@ -import time -from datetime import datetime -import os -import sys import dateutil.parser +import json +import os import requests -try: - from urlparse import urlparse -except: - from urllib.parse import urlparse - -try: - import json -except: - import simplejson as json +import sys +import time +from datetime import datetime +from urllib.parse import urlparse -class IronTokenProvider(object): +class IronTokenProvider: def __init__(self, token): self.token = token @@ -23,7 +16,7 @@ def getToken(self): return self.token -class KeystoneTokenProvider(object): +class KeystoneTokenProvider: def __init__(self, keystone): self.server = keystone["server"] + ("" if keystone["server"].endswith("/") else "/") self.tenant = keystone["tenant"] @@ -32,7 +25,6 @@ def __init__(self, keystone): self.token = None self.local_expires_at_timestamp = 0 - def getToken(self): date_diff = time.mktime(datetime.now().timetuple()) - self.local_expires_at_timestamp if self.token is None or date_diff > -10: @@ -64,7 +56,7 @@ def getToken(self): return self.token -class IronClient(object): +class IronClient: __version__ = "1.2.0" def __init__(self, name, version, product, host=None, project_id=None, @@ -91,34 +83,34 @@ def __init__(self, name, version, product, host=None, project_id=None, None. """ config = { - "host": None, - "protocol": "https", - "port": 443, - "api_version": None, - "project_id": None, - "token": None, - "keystone": None, - "path_prefix": None, - "cloud": None, + "host": None, + "protocol": "https", + "port": 443, + "api_version": None, + "project_id": None, + "token": None, + "keystone": None, + "path_prefix": None, + "cloud": None, } products = { - "iron_worker": { - "host": "worker-aws-us-east-1.iron.io", - "version": 2 - }, - "iron_mq": { - "host": "mq-aws-us-east-1-1.iron.io", - "version": 3 - }, - "iron_cache": { - "host": "cache-aws-us-east-1.iron.io", - "version": 1 - } + "iron_worker": { + "host": "worker-aws-us-east-1.iron.io", + "version": 2 + }, + "iron_mq": { + "host": "mq-aws-us-east-1-1.iron.io", + "version": 3 + }, + "iron_cache": { + "host": "cache-aws-us-east-1.iron.io", + "version": 1 + } } if product in products: config["host"] = products[product]["host"] config["api_version"] = products[product]["version"] - + try: config = configFromFile(config, os.path.expanduser("~/.iron.json"), product) @@ -136,7 +128,7 @@ def __init__(self, name, version, product, host=None, project_id=None, for field in required_fields: if config[field] is None: - raise ValueError("No %s set. %s is a required field." % (field, field)) + raise ValueError(f"No {field} set. {field} is a required field.") keystone_configured = False if config["keystone"] is not None: @@ -152,8 +144,6 @@ def __init__(self, name, version, product, host=None, project_id=None, if config["token"] is None and not keystone_configured: raise ValueError("At least one of token or keystone should be specified.") - - self.name = name self.version = version self.product = product @@ -167,8 +157,8 @@ def __init__(self, name, version, product, host=None, project_id=None, self.cloud = config["cloud"] self.headers = { - "Accept": "application/json", - "User-Agent": "%s (version: %s)" % (self.name, self.version) + "Accept": "application/json", + "User-Agent": f"{self.name} (version: {self.version})" } self.path_prefix = config["path_prefix"] @@ -181,16 +171,16 @@ def __init__(self, name, version, product, host=None, project_id=None, self.path_prefix = url.path.rstrip("/") if self.protocol == "https" and self.port == 443: - self.base_url = "%s://%s%s/%s/" % (self.protocol, self.host, self.path_prefix, self.api_version) + self.base_url = f"{self.protocol}://{self.host}{self.path_prefix}/{self.api_version}/" else: - self.base_url = "%s://%s:%s%s/%s/" % (self.protocol, self.host, + self.base_url = "{}://{}:{}{}/{}/".format(self.protocol, self.host, self.port, self.path_prefix, self.api_version) if self.project_id: - self.base_url += "projects/%s/" % self.project_id + self.base_url += f"projects/{self.project_id}/" def _doRequest(self, url, method, body="", headers={}): if self.token or self.keystone: - headers["Authorization"] = "OAuth %s" % self.token_provider.getToken() + headers["Authorization"] = f"OAuth {self.token_provider.getToken()}" if method == "GET": r = requests.get(url, headers=headers) @@ -226,16 +216,7 @@ def request(self, url, method, body="", headers={}, retry=True): else: headers = self.headers - if not sys.version_info >= (3,) and headers: - headers = dict((k.encode('ascii') if isinstance(k, unicode) else k, - v.encode('ascii') if isinstance(v, unicode) else v) - for k, v in headers.items()) - url = self.base_url + url - if not sys.version_info >= (3,): - if isinstance(url, unicode): - url = url.encode('ascii') - r = self._doRequest(url, method, body, headers) retry_http_codes = [503, 504] @@ -373,14 +354,15 @@ def fromTimestamp(timestamp=None): return timestamp return datetime.fromtimestamp(float(timestamp)) + def configFromFile(config, path, product=None): if path is None: return config if not os.path.exists(path): return config try: - file = open(path, "r") - except IOError: + file = open(path) + except OSError: return config raw = json.loads(file.read()) @@ -400,7 +382,7 @@ def configFromEnv(config, product=None): if product is None: product = "iron" for k in config.keys(): - key = "%s_%s" % (product, k) + key = f"{product}_{k}" if key.upper() in os.environ: config[k] = os.environ[key.upper()] return config @@ -412,5 +394,6 @@ def configFromArgs(config, **kwargs): config[k] = kwargs[k] return config + def intersect(a, b): return list(set(a) & set(b)) diff --git a/setup.py b/setup.py index 101ca53..26045ab 100644 --- a/setup.py +++ b/setup.py @@ -1,35 +1,38 @@ from setuptools import setup -import sys -setup( - name = "iron-core", - py_modules = ["iron_core"], - install_requires=["requests >= 1.1.0", "python-dateutil"], - version = "1.2.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 = """\ +LONG_DESCRIPTION = """ Iron.io common library ---------------------- -This package offers common functions for Iron.io APIs and services. It does not wrap +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.""", +may be built on. Users looking for API wrappers should instead look at +iron_worker_python and iron_worker_mq. +""".strip() + +setup( + name="iron-core", + py_modules=["iron_core"], + install_requires=["requests >= 1.1.0", "python-dateutil"], + version="1.2.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"], + python_requires=">=3.6", + 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=LONG_DESCRIPTION, ) diff --git a/test.py b/test.py index 35ccece..f9af470 100644 --- a/test.py +++ b/test.py @@ -1,12 +1,9 @@ -import iron_core -import unittest +import json import os -from iron_core import KeystoneTokenProvider +import unittest + +import iron_core -try: - import json -except: - import simplejson as json class TestConfig(unittest.TestCase): def setUp(self): @@ -41,12 +38,6 @@ def test_fromArgsMissingProjectID(self): version="0.1.0", product="iron_worker", api_version=2, host="worker-aws-us-east-1.iron.io", token="TEST") - def test_fromArgsProtocolPortMismatch(self): - self.assertRaises(ValueError, iron_core.IronClient, name="Test", - version="0.1.0", product="iron_worker", token="TEST", - api_version=2, project_id="TEST", port=80, - host="worker-aws-us-east-1.iron.io") - def test_fromArgsBareMinimum(self): client = iron_core.IronClient(name="Test", version="0.1.0", product="iron_worker", token="TEST", project_id="TEST2", @@ -72,12 +63,12 @@ def test_fromArgsUseHTTP(self): def test_fromArgsConfigFileGlobal(self): test_config = { - "host": "test-config-host", - "protocol": "test-config-protocol", - "port": "test-config-port", - "api_version": "test-config-api-version", - "project_id": "test-config-project-id", - "token": "test-config-token" + "host": "test-config-host", + "protocol": "test-config-protocol", + "port": "test-config-port", + "api_version": "test-config-api-version", + "project_id": "test-config-project-id", + "token": "test-config-token" } file = open("test_config.json", "w") @@ -98,14 +89,14 @@ def test_fromArgsConfigFileGlobal(self): def test_fromArgsConfigFileProduct(self): test_config = { - "iron_worker": { - "host": "test-config-host", - "protocol": "test-config-protocol", - "port": "test-config-port", - "api_version": "test-config-api-version", - "project_id": "test-config-project-id", - "token": "test-config-token" - } + "iron_worker": { + "host": "test-config-host", + "protocol": "test-config-protocol", + "port": "test-config-port", + "api_version": "test-config-api-version", + "project_id": "test-config-project-id", + "token": "test-config-token" + } } file = open("test_config.json", "w") @@ -113,31 +104,31 @@ def test_fromArgsConfigFileProduct(self): file.close() client = iron_core.IronClient(name="Test", version="0.1.0", - product="iron_worker", config_file="test_config.json") + product="iron_worker", config_file="test_config.json") self.assertEqual(client.host, test_config["iron_worker"]["host"]) self.assertEqual(client.protocol, - test_config["iron_worker"]["protocol"]) + test_config["iron_worker"]["protocol"]) self.assertEqual(client.port, test_config["iron_worker"]["port"]) self.assertEqual(client.api_version, - test_config["iron_worker"]["api_version"]) + test_config["iron_worker"]["api_version"]) self.assertEqual(client.project_id, - test_config["iron_worker"]["project_id"]) + test_config["iron_worker"]["project_id"]) self.assertEqual(client.token, test_config["iron_worker"]["token"]) os.remove("test_config.json") def test_fromArgsConfigFileMixed(self): test_config = { - "host": "test-config-host-global", - "protocol": "test-config-protocol-global", - "port": "test-config-port-global", - "project_id": "test-config-project-id-global", - "iron_worker": { - "api_version": "test-config-api-version-product", - "project_id": "test-config-project-id-product", - "token": "test-config-token-product" - } + "host": "test-config-host-global", + "protocol": "test-config-protocol-global", + "port": "test-config-port-global", + "project_id": "test-config-project-id-global", + "iron_worker": { + "api_version": "test-config-api-version-product", + "project_id": "test-config-project-id-product", + "token": "test-config-token-product" + } } file = open("test_config.json", "w") @@ -145,27 +136,27 @@ def test_fromArgsConfigFileMixed(self): file.close() client = iron_core.IronClient(name="Test", version="0.1.0", - product="iron_worker", config_file="test_config.json") + product="iron_worker", config_file="test_config.json") self.assertEqual(client.host, test_config["host"]) self.assertEqual(client.protocol, test_config["protocol"]) self.assertEqual(client.port, test_config["port"]) self.assertEqual(client.api_version, - test_config["iron_worker"]["api_version"]) + test_config["iron_worker"]["api_version"]) self.assertEqual(client.project_id, - test_config["iron_worker"]["project_id"]) + test_config["iron_worker"]["project_id"]) self.assertEqual(client.token, test_config["iron_worker"]["token"]) os.remove("test_config.json") def test_fromArgsAndArgsConfigFile(self): test_config = { - "host": "test-config-host", - "protocol": "test-config-protocol", - "port": "test-config-port", - "api_version": "test-config-api-version", - "project_id": "test-config-project-id", - "token": "test-config-token" + "host": "test-config-host", + "protocol": "test-config-protocol", + "port": "test-config-port", + "api_version": "test-config-api-version", + "project_id": "test-config-project-id", + "token": "test-config-token" } file = open("test_config.json", "w") @@ -223,7 +214,7 @@ def test_initKeystoneFromJson(self): keystone_required_keys = ["server", "tenant", "username", "password"] config_keystone_keys = client.keystone.keys() - self.assertItemsEqual(config_keystone_keys, keystone_required_keys) + self.assertCountEqual(config_keystone_keys, keystone_required_keys) self.assertEqual(client.project_id, test_keystone_config["project_id"]) remove_test_config("test_keystone_config.json") @@ -242,7 +233,7 @@ def test_initKeystoneFromConstructor(self): keystone_required_keys = ["server", "tenant", "username", "password"] config_keystone_keys = client.keystone.keys() - self.assertItemsEqual(config_keystone_keys, keystone_required_keys) + self.assertCountEqual(config_keystone_keys, keystone_required_keys) def test_ironTokenProvider(self): client = iron_core.IronTokenProvider("iron-token") @@ -255,16 +246,19 @@ def test_checkTrailingSlash(self): "username": "keystone-username", "password": "keystone-password" } - keystone = KeystoneTokenProvider(keystone_data) + keystone = iron_core.KeystoneTokenProvider(keystone_data) self.assertEqual("http://localhost/", keystone.server) + def create_test_config(filename, content): file = open(filename, "w") file.write(json.dumps(content)) file.close() + def remove_test_config(filename): os.remove(filename) + if __name__ == "__main__": unittest.main()