From 29b190d1d28a7cbaa186bfcfcbe63b36161d3709 Mon Sep 17 00:00:00 2001 From: Madison May Date: Thu, 11 Jun 2015 15:57:00 -0400 Subject: [PATCH 1/6] FIX: multi api calls for private cloud --- indicoio/utils/multi.py | 56 ++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/indicoio/utils/multi.py b/indicoio/utils/multi.py index 31725c3..94a4c7d 100644 --- a/indicoio/utils/multi.py +++ b/indicoio/utils/multi.py @@ -6,8 +6,12 @@ from indicoio.utils.errors import IndicoError CLIENT_SERVER_MAP = dict((api, api.strip().replace("_", "").lower()) for api in API_NAMES) SERVER_CLIENT_MAP = dict((v, k) for k, v in CLIENT_SERVER_MAP.iteritems()) +AVAILABLE_APIS = { + 'text': TEXT_APIS, + 'image': IMAGE_APIS +} -def multi(data, type, apis, available, batch=False, **kwargs): +def multi(data, datatype, apis, batch=False, **kwargs): """ Helper to make multi requests of different types. @@ -22,23 +26,39 @@ def multi(data, type, apis, available, batch=False, **kwargs): :rtype: Dictionary of api responses """ # Client side api name checking - strictly only accept func name api + available = AVAILABLE_APIS.get(datatype) invalid_apis = [api for api in apis if api not in available] if invalid_apis: - raise IndicoError("%s are not valid %s APIs. Please reference the available APIs below:\n%s" - % (", ".join(invalid_apis), type, ", ".join(available)) - ) + raise IndicoError( + "%s are not valid %s APIs. Please reference the available APIs below:\n%s" + % (", ".join(invalid_apis), datatype, ", ".join(available)) + ) + # Convert client api names to server names before sending request apis = map(CLIENT_SERVER_MAP.get, apis) - result = api_handler(data, url_params = {"apis":apis, "batch":batch}, **kwargs) + cloud = kwargs.pop("cloud", None) + api_key = kwargs.pop('api_key', None) + result = api_handler( + data, + cloud=cloud, + api='apis', + url_params={ + "apis":apis, + "batch":batch, + "api_key":api_key + }, + **kwargs + ) return handle_response(result) + def handle_response(result): # Parse out the results to a dicionary of api: result return dict((SERVER_CLIENT_MAP[api], parsed_response(api, res)) for api, res in result.iteritems()) -def predict_text(input_text, apis=TEXT_APIS, cloud=None, batch=False, api_key=None, **kwargs): +def predict_text(input_text, apis=TEXT_APIS, **kwargs): """ Given input text, returns the results of specified text apis. Possible apis include: [ 'text_tags', 'political', 'sentiment', 'language' ] @@ -60,19 +80,22 @@ def predict_text(input_text, apis=TEXT_APIS, cloud=None, batch=False, api_key=No :rtype: Dictionary of api responses """ + cloud = kwargs.pop('cloud', None) + batch = kwargs.pop('batch', False) + api_key = kwargs.pop('api_key', None) + return multi( - api="apis", data=input_text, - type="text", - available = TEXT_APIS, + datatype="text", cloud=cloud, batch=batch, api_key=api_key, apis=apis, - **kwargs) + **kwargs + ) -def predict_image(image, apis=IMAGE_APIS, cloud=None, batch=False, api_key=None, **kwargs): +def predict_image(image, apis=IMAGE_APIS, **kwargs): """ Given input image, returns the results of specified image apis. Possible apis include: ['fer', 'facial_features', 'image_features'] @@ -95,16 +118,19 @@ def predict_image(image, apis=IMAGE_APIS, cloud=None, batch=False, api_key=None, :rtype: Dictionary of api responses """ + cloud = kwargs.pop('cloud', None) + batch = kwargs.pop('batch', False) + api_key = kwargs.pop('api_key', None) + return multi( - api="apis", data=image_preprocess(image, batch=batch), - type="image", - available=IMAGE_APIS, + datatype="image", cloud=cloud, batch=batch, api_key=api_key, apis=apis, - **kwargs) + **kwargs + ) def parsed_response(api, response): result = response.get('results', False) From 47133c956f16fdba02ec43037fa45c63cb389b5b Mon Sep 17 00:00:00 2001 From: Madison May Date: Thu, 11 Jun 2015 16:46:51 -0400 Subject: [PATCH 2/6] ADD: sentimenthq --- indicoio/__init__.py | 2 +- indicoio/config.py | 3 ++- indicoio/text/sentiment.py | 23 +++++++++++++++++++++++ indicoio/utils/api.py | 5 +++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/indicoio/__init__.py b/indicoio/__init__.py index 4b0af6b..37fea7c 100644 --- a/indicoio/__init__.py +++ b/indicoio/__init__.py @@ -9,7 +9,7 @@ JSON_HEADERS = { 'version-number': VERSION } -from indicoio.text.sentiment import political, posneg +from indicoio.text.sentiment import political, posneg, sentiment_hq from indicoio.text.sentiment import posneg as sentiment from indicoio.text.lang import language from indicoio.text.tagging import text_tags diff --git a/indicoio/config.py b/indicoio/config.py index 895c76a..0b24332 100644 --- a/indicoio/config.py +++ b/indicoio/config.py @@ -49,7 +49,8 @@ TEXT_APIS = [ 'text_tags', 'political', 'sentiment', - 'language' + 'language', + 'sentiment_hq' ] IMAGE_APIS = [ diff --git a/indicoio/text/sentiment.py b/indicoio/text/sentiment.py index 182908a..5172e40 100644 --- a/indicoio/text/sentiment.py +++ b/indicoio/text/sentiment.py @@ -50,3 +50,26 @@ def posneg(text, cloud=None, batch=False, api_key=None, **kwargs): """ return api_handler(text, cloud=cloud, api="sentiment", url_params={"batch":batch, "api_key":api_key}, **kwargs) + +def sentiment_hq(text, cloud=None, batch=False, api_key=None, **kwargs): + """ + Given input text, returns a scalar estimate of the sentiment of that text. + Values are roughly in the range 0 to 1 with 0.5 indicating neutral sentiment. + For reference, 0 suggests very negative sentiment and 1 suggests very positive sentiment. + + Example usage: + + .. code-block:: python + + >>> from indicoio import sentimenthq + >>> text = 'Thanks everyone for the birthday wishes!! It was a crazy few days ><' + >>> sentiment = sentimenthq(text) + >>> sentiment + 0.6210052967071533 + + :param text: The text to be analyzed. + :type text: str or unicode + :rtype: Float + """ + + return api_handler(text, cloud=cloud, api="sentimenthq", url_params={"batch":batch, "api_key":api_key}, **kwargs) diff --git a/indicoio/utils/api.py b/indicoio/utils/api.py index 71a43b5..e11abe3 100644 --- a/indicoio/utils/api.py +++ b/indicoio/utils/api.py @@ -17,10 +17,15 @@ def api_handler(arg, cloud, api, url_params = {"batch":False, "api_key":None}, * if cloud: host = "%s.indico.domains" % cloud + else: # default to indico public cloud host = config.PUBLIC_API_HOST + # error message for public cloud + if api == 'sentimenthq': + raise IndicoError("The high quality sentiment API is currently in private beta.") + url = config.url_protocol + "//%s/%s" % (host, api) url = url + "/batch" if url_params.get("batch", False) else url url += "?key=%s" % (url_params.get("api_key", None) or config.api_key) From 91b7bfd45473106f85d77f2f99e746582d142b64 Mon Sep 17 00:00:00 2001 From: Madison May Date: Thu, 11 Jun 2015 17:16:25 -0400 Subject: [PATCH 3/6] ADD: informative error message for sentiment_hq API, testing --- indicoio/utils/api.py | 14 ++++++++------ tests/test_remote.py | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/indicoio/utils/api.py b/indicoio/utils/api.py index e11abe3..664e441 100644 --- a/indicoio/utils/api.py +++ b/indicoio/utils/api.py @@ -22,15 +22,17 @@ def api_handler(arg, cloud, api, url_params = {"batch":False, "api_key":None}, * # default to indico public cloud host = config.PUBLIC_API_HOST - # error message for public cloud - if api == 'sentimenthq': - raise IndicoError("The high quality sentiment API is currently in private beta.") - url = config.url_protocol + "//%s/%s" % (host, api) url = url + "/batch" if url_params.get("batch", False) else url url += "?key=%s" % (url_params.get("api_key", None) or config.api_key) - if "apis" in url_params: - url += "&apis=%s" % ",".join(url_params["apis"]) + apis = url_params.get("apis", []) + if apis: + url += "&apis=%s" % ",".join(apis) + + # private beta + if host == config.PUBLIC_API_HOST: + if (api == 'sentimenthq') or ('sentimenthq' in apis): + raise IndicoError("The high quality sentiment API is currently in private beta.") response = requests.post(url, data=json_data, headers=JSON_HEADERS) if response.status_code == 503 and cloud != None: diff --git a/tests/test_remote.py b/tests/test_remote.py index dcb0f81..ef66a96 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -9,6 +9,7 @@ from indicoio import config from indicoio import political, sentiment, fer, facial_features, language, image_features, text_tags from indicoio import batch_political, batch_sentiment, batch_fer, batch_facial_features from indicoio import batch_language, batch_image_features, batch_text_tags +from indicoio import sentiment_hq, batch_sentiment_hq from indicoio import predict_image, predict_text, batch_predict_image, batch_predict_text from indicoio.utils.errors import IndicoError @@ -37,6 +38,13 @@ class BatchAPIRun(unittest.TestCase): self.assertTrue(isinstance(response, list)) self.assertTrue(response[0] < 0.5) + # TODO: uncomment once the high quality sentiment API is publicly released + # def test_batch_sentiment_hq(self): + # test_data = ['Worst song ever', 'Best song ever'] + # response = batch_sentiment_hq(test_data, api_key=self.api_key) + # self.assertTrue(isinstance(response, list)) + # self.assertTrue(response[0] < 0.5) + def test_batch_political(self): test_data = ["Guns don't kill people, people kill people."] response = batch_political(test_data, api_key=self.api_key) @@ -220,6 +228,19 @@ class FullAPIRun(unittest.TestCase): self.assertTrue(isinstance(response, float)) self.assertTrue(response > 0.5) + # TODO: uncomment when the high quality sentiment API is publicly released + # def test_sentiment_hq(self): + # test_string = "Worst song ever." + # response = sentiment_hq(test_string) + + # self.assertTrue(isinstance(response, float)) + # self.assertTrue(response < 0.5) + + # test_string = "Best song ever." + # response = sentiment_hq(test_string) + # self.assertTrue(isinstance(response, float)) + # self.assertTrue(response > 0.5) + def test_good_fer(self): fer_set = set(['Angry', 'Sad', 'Neutral', 'Surprise', 'Fear', 'Happy']) test_face = generate_array((48,48)) From 5c36cf92b751dc3d8c031b6e74b62e375853912a Mon Sep 17 00:00:00 2001 From: Madison May Date: Thu, 11 Jun 2015 18:04:17 -0400 Subject: [PATCH 4/6] UPDATE: Tick version number --- indicoio/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indicoio/__init__.py b/indicoio/__init__.py index 37fea7c..b766713 100644 --- a/indicoio/__init__.py +++ b/indicoio/__init__.py @@ -1,6 +1,6 @@ from functools import partial -Version, version, __version__, VERSION = ('0.7.0',) * 4 +Version, version, __version__, VERSION = ('0.7.1',) * 4 JSON_HEADERS = { 'Content-type': 'application/json', From ce601a5d9f83fd27ff174d7f70377563715c4bad Mon Sep 17 00:00:00 2001 From: Madison May Date: Thu, 11 Jun 2015 18:05:21 -0400 Subject: [PATCH 5/6] UPDATE: CHANGES.txt --- CHANGES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.txt b/CHANGES.txt index d026e5d..e39c937 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -24,3 +24,4 @@ v0.5.2, Tue March 7 -- Required API keys, configuration settings v0.5.3, Wed Apr 15 -- Added scipy to requirements, edited Readme to not break pypi page v0.6.0, Thu May 29 -- Remove numpy / scipy dependency in favor of Pillow v0.7.0, Tue Jun 9 -- Added support for calling multiple APIs in a single function and accepting filenames as image API inputs +v0.7.1 Thu Jun 11 -- High quality sentiment API for private beta, fix for multi API support From 0ece296842d0ed8c6288a5243158405bdda3477b Mon Sep 17 00:00:00 2001 From: Madison May Date: Thu, 11 Jun 2015 18:19:12 -0400 Subject: [PATCH 6/6] FIX: remove sentiment_hq from multi-api tests --- tests/test_remote.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_remote.py b/tests/test_remote.py index ef66a96..0e80b0b 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -13,6 +13,9 @@ from indicoio import sentiment_hq, batch_sentiment_hq from indicoio import predict_image, predict_text, batch_predict_image, batch_predict_text from indicoio.utils.errors import IndicoError +# TODO: remove once sentiment_hq is added to the public API +config.TEXT_APIS.remove("sentiment_hq") + DIR = os.path.dirname(os.path.realpath(__file__)) class BatchAPIRun(unittest.TestCase):