mirror of
https://github.com/wassname/IndicoIo-python.git
synced 2026-08-12 11:40:34 +08:00
Merge pull request #55 from IndicoDataSolutions/Chris/multi-api
ADD: Endpoint for multi api requests
This commit is contained in:
+4
-11
@@ -16,18 +16,11 @@ from indicoio.text.tagging import text_tags
|
|||||||
from indicoio.images.fer import fer
|
from indicoio.images.fer import fer
|
||||||
from indicoio.images.features import facial_features
|
from indicoio.images.features import facial_features
|
||||||
from indicoio.images.features import image_features
|
from indicoio.images.features import image_features
|
||||||
|
from indicoio.utils.multi import predict_image, predict_text
|
||||||
|
|
||||||
apis = [
|
from indicoio.config import API_NAMES
|
||||||
'political',
|
|
||||||
'posneg',
|
apis = dict((api, globals().get(api)) for api in API_NAMES)
|
||||||
'sentiment',
|
|
||||||
'language',
|
|
||||||
'fer',
|
|
||||||
'facial_features',
|
|
||||||
'image_features',
|
|
||||||
'text_tags'
|
|
||||||
]
|
|
||||||
apis = dict((api, globals().get(api)) for api in apis)
|
|
||||||
|
|
||||||
for api in apis:
|
for api in apis:
|
||||||
globals()[api] = partial(apis[api])
|
globals()[api] = partial(apis[api])
|
||||||
|
|||||||
@@ -45,11 +45,26 @@ class Settings(ConfigParser.ConfigParser):
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TEXT_APIS = [
|
||||||
|
'text_tags',
|
||||||
|
'political',
|
||||||
|
'sentiment',
|
||||||
|
'language'
|
||||||
|
]
|
||||||
|
|
||||||
|
IMAGE_APIS = [
|
||||||
|
'fer',
|
||||||
|
'facial_features',
|
||||||
|
'image_features'
|
||||||
|
]
|
||||||
|
|
||||||
|
API_NAMES = IMAGE_APIS + TEXT_APIS + ["predict_text", "predict_image"]
|
||||||
|
|
||||||
SETTINGS = Settings(files=[
|
SETTINGS = Settings(files=[
|
||||||
os.path.expanduser("~/.indicorc"),
|
os.path.expanduser("~/.indicorc"),
|
||||||
os.path.join(os.getcwd(), '.indicorc')
|
os.path.join(os.getcwd(), '.indicorc')
|
||||||
])
|
])
|
||||||
|
|
||||||
api_key = SETTINGS.api_key()
|
api_key = SETTINGS.api_key()
|
||||||
cloud = SETTINGS.cloud()
|
cloud = SETTINGS.cloud()
|
||||||
PUBLIC_API_HOST = 'apiv2.indico.io'
|
PUBLIC_API_HOST = 'apiv2.indico.io'
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def facial_features(image, cloud=None, batch=False, api_key=None, **kwargs):
|
|||||||
:rtype: List containing feature responses
|
:rtype: List containing feature responses
|
||||||
"""
|
"""
|
||||||
image = image_preprocess(image, batch=batch)
|
image = image_preprocess(image, batch=batch)
|
||||||
return api_handler(image, cloud=cloud, api="facialfeatures", batch=batch, api_key=api_key, **kwargs)
|
return api_handler(image, cloud=cloud, api="facialfeatures", url_params={"batch":batch, "api_key":api_key}, **kwargs)
|
||||||
|
|
||||||
def image_features(image, cloud=None, batch=False, api_key=None, **kwargs):
|
def image_features(image, cloud=None, batch=False, api_key=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -58,4 +58,4 @@ def image_features(image, cloud=None, batch=False, api_key=None, **kwargs):
|
|||||||
:rtype: List containing features
|
:rtype: List containing features
|
||||||
"""
|
"""
|
||||||
image = image_preprocess(image, batch=batch, size=(64,64))
|
image = image_preprocess(image, batch=batch, size=(64,64))
|
||||||
return api_handler(image, cloud=cloud, api="imagefeatures", batch=batch, api_key=api_key, **kwargs)
|
return api_handler(image, cloud=cloud, api="imagefeatures", url_params={"batch":batch, "api_key":api_key}, **kwargs)
|
||||||
|
|||||||
@@ -27,4 +27,4 @@ def fer(image, cloud=None, batch=False, api_key=None, **kwargs):
|
|||||||
:rtype: Dictionary containing emotion probability pairs
|
:rtype: Dictionary containing emotion probability pairs
|
||||||
"""
|
"""
|
||||||
image = image_preprocess(image, batch=batch)
|
image = image_preprocess(image, batch=batch)
|
||||||
return api_handler(image, cloud=cloud, api="fer", batch=batch, api_key=api_key, **kwargs)
|
return api_handler(image, cloud=cloud, api="fer", url_params={"batch":batch, "api_key":api_key}, **kwargs)
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ def language(text, cloud=None, batch=False, api_key=None, **kwargs):
|
|||||||
:rtype: Dictionary of language probability pairs
|
:rtype: Dictionary of language probability pairs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return api_handler(text, cloud=cloud, api="language", batch=batch, api_key=api_key, **kwargs)
|
return api_handler(text, cloud=cloud, api="language", url_params={"batch":batch, "api_key":api_key}, **kwargs)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ def political(text, cloud=None, batch=False, api_key=None, **kwargs):
|
|||||||
:rtype: Dictionary of party probability pairs
|
:rtype: Dictionary of party probability pairs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return api_handler(text, cloud=cloud, api="political", batch=batch, api_key=api_key, **kwargs)
|
return api_handler(text, cloud=cloud, api="political", url_params={"batch":batch, "api_key":api_key}, **kwargs)
|
||||||
|
|
||||||
def posneg(text, cloud=None, batch=False, api_key=None, **kwargs):
|
def posneg(text, cloud=None, batch=False, api_key=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -49,4 +49,4 @@ def posneg(text, cloud=None, batch=False, api_key=None, **kwargs):
|
|||||||
:rtype: Float
|
:rtype: Float
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return api_handler(text, cloud=cloud, api="sentiment", batch=batch, api_key=api_key, **kwargs)
|
return api_handler(text, cloud=cloud, api="sentiment", url_params={"batch":batch, "api_key":api_key}, **kwargs)
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ def text_tags(text, cloud=None, batch=False, api_key=None, **kwargs):
|
|||||||
:rtype: Dictionary of class probability pairs
|
:rtype: Dictionary of class probability pairs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return api_handler(text, cloud=cloud, api="texttags", batch=batch, api_key=api_key, **kwargs)
|
return api_handler(text, cloud=cloud, api="texttags", url_params={"batch":batch, "api_key":api_key}, **kwargs)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from indicoio import config
|
|||||||
B64_PATTERN = re.compile("^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)")
|
B64_PATTERN = re.compile("^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)")
|
||||||
|
|
||||||
|
|
||||||
def api_handler(arg, cloud, api, batch=False, api_key=None, **kwargs):
|
def api_handler(arg, cloud, api, url_params = {"batch":False, "api_key":None}, **kwargs):
|
||||||
data = {'data': arg}
|
data = {'data': arg}
|
||||||
data.update(**kwargs)
|
data.update(**kwargs)
|
||||||
json_data = json.dumps(data)
|
json_data = json.dumps(data)
|
||||||
@@ -21,13 +21,11 @@ def api_handler(arg, cloud, api, batch=False, api_key=None, **kwargs):
|
|||||||
# default to indico public cloud
|
# default to indico public cloud
|
||||||
host = config.PUBLIC_API_HOST
|
host = config.PUBLIC_API_HOST
|
||||||
|
|
||||||
if not api_key:
|
|
||||||
api_key = config.api_key
|
|
||||||
|
|
||||||
url = config.url_protocol + "//%s/%s" % (host, api)
|
url = config.url_protocol + "//%s/%s" % (host, api)
|
||||||
url = url + "/batch" if batch else url
|
url = url + "/batch" if url_params.get("batch", False) else url
|
||||||
url += "?key=%s" % api_key
|
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"])
|
||||||
|
|
||||||
response = requests.post(url, data=json_data, headers=JSON_HEADERS)
|
response = requests.post(url, data=json_data, headers=JSON_HEADERS)
|
||||||
if response.status_code == 503 and cloud != None:
|
if response.status_code == 503 and cloud != None:
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
from indicoio.config import TEXT_APIS, IMAGE_APIS, API_NAMES
|
||||||
|
from indicoio.utils import api_handler
|
||||||
|
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
def multi(data, type, apis, available, 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 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
|
||||||
|
:rtype: Dictionary of api responses
|
||||||
|
"""
|
||||||
|
# Client side api name checking - strictly only accept func name api
|
||||||
|
invalid_apis = [api for api in apis if api not in available]
|
||||||
|
if invalid_apis:
|
||||||
|
raise ValueError("%s are not valid %s APIs. Please reference the available APIs below:\n%s"
|
||||||
|
% (", ".join(invalid_apis), type, ", ".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)
|
||||||
|
return handle_response(result)
|
||||||
|
|
||||||
|
def handle_response(result):
|
||||||
|
try:
|
||||||
|
# Parse out the results to a dicionary of api: result
|
||||||
|
return dict((SERVER_CLIENT_MAP[api], parsed_response(res))
|
||||||
|
for api, res in result.iteritems())
|
||||||
|
except KeyError:
|
||||||
|
for api in result:
|
||||||
|
if "error" in result[api]:
|
||||||
|
raise ValueError(result[api]["error"])
|
||||||
|
raise Exception("Sorry, %s API returned an unexpected response:\n%s" % (api, result[api]))
|
||||||
|
|
||||||
|
|
||||||
|
def predict_text(input_text, apis=TEXT_APIS, cloud=None, batch=False, api_key=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Given input text, returns the results of specified text apis. Possible apis
|
||||||
|
include: [ 'text_tags', 'political', 'sentiment', 'language' ]
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
>>> 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"]
|
||||||
|
>>> sentiment_results = results["sentiment"]
|
||||||
|
|
||||||
|
:param text: The text to be analyzed.
|
||||||
|
:param apis: List of apis to use.
|
||||||
|
:type text: str or unicode
|
||||||
|
:type apis: list of str
|
||||||
|
:rtype: Dictionary of api responses
|
||||||
|
"""
|
||||||
|
|
||||||
|
return multi(
|
||||||
|
api="apis",
|
||||||
|
data=input_text,
|
||||||
|
type="text",
|
||||||
|
available = TEXT_APIS,
|
||||||
|
cloud=cloud,
|
||||||
|
batch=batch,
|
||||||
|
api_key=api_key,
|
||||||
|
apis=apis,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def predict_image(image, apis=IMAGE_APIS, cloud=None, batch=False, api_key=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Given input image, returns the results of specified image apis. Possible apis
|
||||||
|
include: ['fer', 'facial_features', 'image_features']
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
>>> import indicoio
|
||||||
|
>>> import numpy as np
|
||||||
|
>>> face = np.zeros((48,48)).tolist()
|
||||||
|
>>> results = indicoio.image(image = face, apis = ["fer", "facial_features"])
|
||||||
|
>>> fer = results["fer"]
|
||||||
|
>>> facial_features = results["facial_features"]
|
||||||
|
|
||||||
|
:param text: The text to be analyzed.
|
||||||
|
:param apis: List of apis to use.
|
||||||
|
:type text: str or unicode
|
||||||
|
:type apis: list of str
|
||||||
|
:rtype: Dictionary of api responses
|
||||||
|
"""
|
||||||
|
|
||||||
|
return multi(
|
||||||
|
api="apis",
|
||||||
|
data=image,
|
||||||
|
type="image",
|
||||||
|
available=IMAGE_APIS,
|
||||||
|
cloud=cloud,
|
||||||
|
batch=batch,
|
||||||
|
api_key=api_key,
|
||||||
|
apis=apis,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def parsed_response(response):
|
||||||
|
result = response.get('results') or response.get('error', False)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
raise KeyError
|
||||||
@@ -9,6 +9,7 @@ from indicoio import config
|
|||||||
from indicoio import political, sentiment, fer, facial_features, language, image_features, text_tags
|
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_political, batch_sentiment, batch_fer, batch_facial_features
|
||||||
from indicoio import batch_language, batch_image_features, batch_text_tags
|
from indicoio import batch_language, batch_image_features, batch_text_tags
|
||||||
|
from indicoio import predict_image, predict_text, batch_predict_image, batch_predict_text
|
||||||
|
|
||||||
DIR = os.path.dirname(os.path.realpath(__file__))
|
DIR = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
@@ -113,6 +114,58 @@ class BatchAPIRun(unittest.TestCase):
|
|||||||
self.assertTrue(isinstance(response, list))
|
self.assertTrue(isinstance(response, list))
|
||||||
self.assertTrue(response[0]['English'] > 0.25)
|
self.assertTrue(response[0]['English'] > 0.25)
|
||||||
|
|
||||||
|
def test_multi_api_image(self):
|
||||||
|
test_data = generate_array((48,48))
|
||||||
|
response = predict_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)
|
||||||
|
|
||||||
|
self.assertTrue(isinstance(response, dict))
|
||||||
|
self.assertTrue(set(response.keys()) == set(config.TEXT_APIS))
|
||||||
|
|
||||||
|
def test_batch_multi_api_image(self):
|
||||||
|
test_data = [generate_array((48,48))]
|
||||||
|
response = batch_predict_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_batch_multi_api_text(self):
|
||||||
|
test_data = ['clearly an english sentence']
|
||||||
|
response = batch_predict_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 = batch_predict_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(ValueError,
|
||||||
|
batch_predict_text,
|
||||||
|
"this shouldn't work",
|
||||||
|
apis=["sentiment", "somethingbad"])
|
||||||
|
|
||||||
|
def test_multi_bad_mixed_api(self):
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
predict_text,
|
||||||
|
"this shouldn't work",
|
||||||
|
apis=["fer", "sentiment", "facial_features"])
|
||||||
|
def test_batch_multi_bad_mixed_api(self):
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
batch_predict_text,
|
||||||
|
["this shouldn't work"],
|
||||||
|
apis=["fer", "sentiment", "facial_features"])
|
||||||
|
|
||||||
def test_batch_set_cloud(self):
|
def test_batch_set_cloud(self):
|
||||||
test_data = ['clearly an english sentence']
|
test_data = ['clearly an english sentence']
|
||||||
self.assertRaises(ConnectionError,
|
self.assertRaises(ConnectionError,
|
||||||
|
|||||||
Reference in New Issue
Block a user