diff --git a/CHANGES.txt b/CHANGES.txt index 5d57624..9ba15a4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -34,3 +34,5 @@ v0.8.0 Fri Jul 10 -- Add Content Filtering API, Named Entities API, Facial Emoti v0.8.1 Wed Jul 22 -- Add Sentiment HQ to predict_text API v0.9.0 Tue Jul 28 -- Deprecate batch function calls in favor of type inference v0.9.1 Mon Aug 3 -- Add Facial Localization API, image resizing updates +v0.9.2 Fri Aug 14 -- Add intersections API, analyzeText, analyzeImage + diff --git a/indicoio/__init__.py b/indicoio/__init__.py index 68552cf..0a4022c 100644 --- a/indicoio/__init__.py +++ b/indicoio/__init__.py @@ -1,7 +1,7 @@ from functools import wraps, partial import warnings -Version, version, __version__, VERSION = ('0.9.1',) * 4 +Version, version, __version__, VERSION = ('0.9.2',) * 4 JSON_HEADERS = { 'Content-type': 'application/json', @@ -22,7 +22,7 @@ from indicoio.images.features import facial_features from indicoio.images.faciallocalization import facial_localization from indicoio.images.features import image_features from indicoio.images.filtering import content_filtering -from indicoio.utils.multi import predict_image, predict_text +from indicoio.utils.multi import analyze_image, analyze_text, intersections from indicoio.config import API_NAMES diff --git a/indicoio/config.py b/indicoio/config.py index 4d34d28..52062f2 100644 --- a/indicoio/config.py +++ b/indicoio/config.py @@ -63,7 +63,13 @@ IMAGE_APIS = [ 'content_filtering' ] -API_NAMES = IMAGE_APIS + TEXT_APIS + ["predict_text", "predict_image"] +OTHER_APIS = [ + "analyze_text", + "analyze_image", + "intersections" +] + +API_NAMES = IMAGE_APIS + TEXT_APIS + OTHER_APIS SETTINGS = Settings(files=[ os.path.expanduser("~/.indicorc"), diff --git a/indicoio/utils/api.py b/indicoio/utils/api.py index 14c6555..9907308 100644 --- a/indicoio/utils/api.py +++ b/indicoio/utils/api.py @@ -19,7 +19,6 @@ def api_handler(arg, cloud, api, url_params=None, **kwargs): cloud = cloud or config.cloud host = "%s.indico.domains" % cloud if cloud else config.PUBLIC_API_HOST url = create_url(host, api, url_params) - response = requests.post(url, data=json_data, headers=JSON_HEADERS) if response.status_code == 503 and cloud != None: diff --git a/indicoio/utils/multi.py b/indicoio/utils/multi.py index a399969..68a3727 100644 --- a/indicoio/utils/multi.py +++ b/indicoio/utils/multi.py @@ -11,19 +11,59 @@ AVAILABLE_APIS = { 'image': IMAGE_APIS } +def invert_dictionary(d): + return { + element: key for key, values in d.iteritems() + for element in values + } + +API_TYPES = invert_dictionary(AVAILABLE_APIS) + + +def intersections(data, apis = None, **kwargs): + """ + Helper to make multi requests of different types. + + :param data: Data to be sent in API request + :param type: String type of API request + :rtype: Dictionary of api responses + """ + # Client side api name checking + + # remove auto-inserted batch param + kwargs.pop('batch', None) + + if not isinstance(apis, list) or len(apis) != 2: + raise IndicoError("Argument 'apis' must be of length 2") + if isinstance(data, list) and len(data) < 3: + raise IndicoError( + "At least 3 examples are required to use the intersections API" + ) + + api_types = map(API_TYPES.get, apis) + if api_types[0] != api_types[1]: + raise IndicoError( + "Both `apis` must accept the same kind of input to use the intersections API" + ) + + cloud = kwargs.get("cloud", None) + + url_params = { + 'batch': False, + 'api_key': kwargs.pop('api_key', None), + 'apis': apis + } + + return api_handler(data, cloud=cloud, api="apis/intersections", url_params=url_params, **kwargs) def multi(data, datatype, apis, batch=False, **kwargs): """ Helper to make multi requests of different types. - :param data: data to be sent in JSON. - :param type: String type of API request + :param data: Data to be sent in API request + :param datatype: String type of API request :param apis: List of apis to use. - :param apis: List of apis available for use. - :type data: str or image - :type type: str or unicode - :type apis: list of str - :type available: list of str + :param batch: Is this a batch request? :rtype: Dictionary of api responses """ # Client side api name checking - strictly only accept func name api @@ -36,13 +76,12 @@ def multi(data, datatype, apis, batch=False, **kwargs): ) # Convert client api names to server names before sending request - apis = map(CLIENT_SERVER_MAP.get, apis) cloud = kwargs.pop("cloud", None) api_key = kwargs.pop('api_key', None) result = api_handler( data, cloud=cloud, - api='apis', + api='apis/multiapi', url_params={ "apis":apis, "batch":batch, @@ -55,11 +94,11 @@ def multi(data, datatype, apis, batch=False, **kwargs): def handle_response(result): # Parse out the results to a dicionary of api: result - return dict((SERVER_CLIENT_MAP[api], parsed_response(api, res)) + return dict((api, parsed_response(api, res)) for api, res in result.iteritems()) -def predict_text(input_text, apis=TEXT_APIS, **kwargs): +def analyze_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' ] @@ -70,8 +109,8 @@ def predict_text(input_text, apis=TEXT_APIS, **kwargs): >>> import indicoio >>> text = 'Monday: Delightful with mostly sunny skies. Highs in the low 70s.' - >>> results = indicoio.text(data = text, apis = ["language", "sentiment"]) - >>> language_results = results["langauge"] + >>> results = indicoio.analyze_text(data = text, apis = ["language", "sentiment"]) + >>> language_results = results["language"] >>> sentiment_results = results["sentiment"] :param text: The text to be analyzed. @@ -96,7 +135,7 @@ def predict_text(input_text, apis=TEXT_APIS, **kwargs): ) -def predict_image(image, apis=IMAGE_APIS, **kwargs): +def analyze_image(image, apis=IMAGE_APIS, **kwargs): """ Given input image, returns the results of specified image apis. Possible apis include: ['fer', 'facial_features', 'image_features'] @@ -108,7 +147,7 @@ def predict_image(image, apis=IMAGE_APIS, **kwargs): >>> import indicoio >>> import numpy as np >>> face = np.zeros((48,48)).tolist() - >>> results = indicoio.image(image = face, apis = ["fer", "facial_features"]) + >>> results = indicoio.analyze_image(image = face, apis = ["fer", "facial_features"]) >>> fer = results["fer"] >>> facial_features = results["facial_features"] diff --git a/setup.py b/setup.py index 8b24c23..1f76b8b 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ except ImportError: setup( name="IndicoIo", - version="0.9.1", + version="0.9.2", packages=[ "indicoio", "indicoio.text", diff --git a/tests/test_remote.py b/tests/test_remote.py index 8238aaf..c16a5de 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -15,7 +15,7 @@ from indicoio import keywords, batch_keywords from indicoio import sentiment_hq, batch_sentiment_hq from indicoio import twitter_engagement, batch_twitter_engagement from indicoio import named_entities, batch_named_entities -from indicoio import predict_image, predict_text, batch_predict_image, batch_predict_text +from indicoio import intersections, analyze_image, analyze_text, batch_analyze_image, batch_analyze_text from indicoio.utils.errors import IndicoError DIR = os.path.dirname(os.path.realpath(__file__)) @@ -158,7 +158,7 @@ class BatchAPIRun(unittest.TestCase): def test_batch_multi_api_image(self): test_data = [os.path.normpath(os.path.join(DIR, "data/48by48.png")), os.path.normpath(os.path.join(DIR, "data/48by48.png"))] - response = predict_image(test_data, apis=config.IMAGE_APIS, api_key=self.api_key) + response = analyze_image(test_data, apis=config.IMAGE_APIS, api_key=self.api_key) self.assertTrue(isinstance(response, dict)) self.assertTrue(set(response.keys()) == set(config.IMAGE_APIS)) @@ -166,32 +166,32 @@ class BatchAPIRun(unittest.TestCase): def test_batch_multi_api_text(self): test_data = ['clearly an english sentence'] - response = predict_text(test_data, apis=config.TEXT_APIS, api_key=self.api_key) + response = analyze_text(test_data, apis=config.TEXT_APIS, api_key=self.api_key) self.assertTrue(isinstance(response, dict)) self.assertTrue(set(response.keys()) == set(config.TEXT_APIS)) def test_default_multi_api_text(self): test_data = ['clearly an english sentence'] - response = predict_text(test_data, api_key=self.api_key) + response = analyze_text(test_data, api_key=self.api_key) self.assertTrue(isinstance(response, dict)) self.assertTrue(set(response.keys()) == set(config.TEXT_APIS)) def test_multi_api_bad_api(self): self.assertRaises(IndicoError, - predict_text, + analyze_text, "this shouldn't work", apis=["sentiment", "somethingbad"]) def test_multi_bad_mixed_api(self): self.assertRaises(IndicoError, - predict_text, + analyze_text, "this shouldn't work", apis=["fer", "sentiment", "facial_features"]) def test_batch_multi_bad_mixed_api(self): self.assertRaises(IndicoError, - predict_text, + analyze_text, ["this shouldn't work"], apis=["fer", "sentiment", "facial_features"]) @@ -470,18 +470,58 @@ class FullAPIRun(unittest.TestCase): def test_multi_api_image(self): test_data = os.path.normpath(os.path.join(DIR, "data/48by48.png")) - response = predict_image(test_data, apis=config.IMAGE_APIS, api_key=self.api_key) + response = analyze_image(test_data, apis=config.IMAGE_APIS, api_key=self.api_key) self.assertTrue(isinstance(response, dict)) self.assertTrue(set(response.keys()) == set(config.IMAGE_APIS)) def test_multi_api_text(self): test_data = 'clearly an english sentence' - response = predict_text(test_data, apis=config.TEXT_APIS, api_key=self.api_key) + response = analyze_text(test_data, apis=config.TEXT_APIS, api_key=self.api_key) self.assertTrue(isinstance(response, dict)) self.assertTrue(set(response.keys()) == set(config.TEXT_APIS)) + def test_intersections_not_enough_data(self): + test_data = ['test_Data'] + self.assertRaises( + IndicoError, + intersections, + test_data, + apis=['text_tags', 'sentiment'] + ) + + def test_intersections_wrong_number_of_apis(self): + test_data = ['test data']*3 + self.assertRaises( + IndicoError, + intersections, + test_data, + apis=['text_tags', 'sentiment', 'language'] + ) + + def test_intersections_bad_api_type(self): + test_data = ['test data']*3 + self.assertRaises( + IndicoError, + intersections, + test_data, + apis=['text_tags', 'fer'] + ) + + def test_intersections_valid_input(self): + test_data = ['test data']*3 + apis = ['text_tags', 'sentiment'] + results = intersections(test_data, apis=apis) + assert set(results.keys()) < set(apis) + + def test_intersections_valid_raw_input(self): + test_data = { + 'sentiment': [0.1, 0.2, 0.3], + 'twitter_engagement': [0.1, 0.2, 0.3] + } + results = intersections(test_data, apis=['sentiment', 'twitter_engagement']) + assert set(results.keys()) < set(test_data.keys()) def test_language(self): language_set = set([