Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "refer to the server for languages" #18

Merged
merged 1 commit into from
Oct 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clarifai/client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from __future__ import absolute_import

from .client import ClarifaiApi, ApiError, ApiThrottledError, ApiBadRequestError, ApiClientError
from .client import ClarifaiApi, ApiError, ApiThrottledError, ApiBadRequestError
74 changes: 55 additions & 19 deletions clarifai/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def iteritems(d):
def iteritems(d):
return d.iteritems()

#logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)


Expand All @@ -42,11 +43,11 @@ def __str__(self):
def __repr__(self):
return "Error: '%s'" % str(self.msg)


class ApiClientError(ApiError):
"""Thrown when client side validation fails"""
pass


class ApiThrottledError(Exception):
"""The usage limit throttle was hit. Client should wait for wait_seconds before retrying."""

Expand Down Expand Up @@ -76,14 +77,37 @@ class ClarifaiApi(object):
app_id: the client_id for an application you've created in your Clarifai account.
app_secret: the client_secret for the same application.
base_url: Base URL of the API endpoints.
model: Name of the recognition model to query. Defaults to None so that server side defaults
in your app settings are used.
model: Name of the recognition model to query. Defaults to None so that server side defaults
in your app settings are used.
wait_on_throttle: When the API returns a 429 throttled error, sleep for the amount of time
reported in the X-Throttle-Wait-Seconds HTTP response header.
language: set the default language using it's two letter (with options -XX variant) ISO 639-1
code to use for all requests. Defaults to None so that server side defaults in your app settings
are used.
"""
_SUPPORTED_LANGUAGES = {
'ar': 'Arabic',
'bn': 'Bengali',
'da': 'Danish',
'de': 'German',
'en': 'English',
'es': 'Spanish',
'fi': 'Finnish',
'fr': 'French',
'hi': 'Hindi',
'it': 'Italian',
'ja': 'Japanese',
'nl': 'Dutch',
'no': 'Norwegian',
'pa': 'Punjabi',
'pl': 'Polish',
'pt': 'Portuguese',
'ru': 'Russian',
'sv': 'Swedish',
'tr': 'Turkish',
'zh': 'Chinese (Simplified)',
'zh-TW': 'Chinese (Traditional)'
}

def __init__(self, app_id=None, app_secret=None, base_url='https://api.clarifai.com',
model=None, wait_on_throttle=True, language=None):
Expand All @@ -106,8 +130,7 @@ def __init__(self, app_id=None, app_secret=None, base_url='https://api.clarifai.
'multiop': "/".join([self._base_url, '%s/multiop/' % API_VERSION]),
'feedback': "/".join([self._base_url, '%s/feedback/' % API_VERSION]),
'token': "/".join([self._base_url, '%s/token/' % API_VERSION]),
'info': "/".join([self._base_url, '%s/info/' % API_VERSION]),
'languages': "/".join([self._base_url, '%s/info/languages' % API_VERSION])
'info': "/".join([self._base_url, '%s/info/' % API_VERSION])
}
self.access_token = None
self.api_info = None
Expand All @@ -121,15 +144,25 @@ def language(self):

@language.setter
def language(self, lang_code):
self._language = self._sanitize_param(lang_code, default=None)
self._language = self._parse_language(lang_code)

def get_languages(self):
response = self._parse_response(self._get_raw_response(
self._get_json_headers,
self._get_json_response,
self._url_for_op('languages'),
{}))
return response['languages']
def _parse_language(self, lang_code):
"""
Checks to see if the language code is supported and sanitizes it.

Args:
lang_code: language code

Returns:
lang_code: validated and sanitized language code

Raises:
ApiClientError: if the language code that was provided is not supported
"""
if lang_code is not None and lang_code not in self._SUPPORTED_LANGUAGES:
raise ApiClientError('Invalid language code {code}. Should be one of {supported}'
.format(code=lang_code, supported=self._SUPPORTED_LANGUAGES.items()))
return self._sanitize_param(lang_code, default=None)

def get_access_token(self, renew=False):
""" Get an access token using your app_id and app_secret.
Expand Down Expand Up @@ -176,6 +209,9 @@ def get_info(self):
self.api_info = response['results']
return self.api_info

def get_languages(self):
return self._SUPPORTED_LANGUAGES

def _url_for_op(self, ops):
if not isinstance(ops, list):
ops = [ops]
Expand All @@ -202,7 +238,7 @@ def tag(self, files, model=None, local_ids=None, meta=None, select_classes=None,
back in order).
meta: a string of any extra information to accompany the request. This has to be a string, so
if passing structured data, pass a json.dumps(meta) string.
select_classes: to select only a subset of all possible classes, enter a comma separated list
select_classes: to select only a subset of all possible classes, enter a comma separated list
of classes you want to predict. Ex: "dog,cat,tree,car,boat"
language: set the default language using it's two letter (with options -XX variant) ISO 639-1
code to use for all requests.
Expand All @@ -217,7 +253,7 @@ def tag(self, files, model=None, local_ids=None, meta=None, select_classes=None,
clarifai_api.tag([open('/path/to/local/image.jpeg'),
open('/path/to/local/image2.jpeg')])
"""
return self._multi_data_op(files, ['tag'], model=model, local_ids=local_ids, meta=meta,
return self._multi_data_op(files, ['tag'], model=model, local_ids=local_ids, meta=meta,
select_classes=select_classes,
language=language)

Expand Down Expand Up @@ -273,7 +309,7 @@ def tag_and_embed(self, files, model=None, local_ids=None, meta=None, select_cla
back in order).
meta: a string of any extra information to accompany the request. This has to be a string, so
if passing structured data, pass a json.dumps(meta) string.
select_classes: to select only a subset of all possible classes, enter a comma separated list
select_classes: to select only a subset of all possible classes, enter a comma separated list
of classes you want to predict. Ex: "dog,cat,tree,car,boat"
language: set the default language using it's two letter (with options -XX variant) ISO 639-1
code to use for all requests.
Expand Down Expand Up @@ -307,7 +343,7 @@ def tag_urls(self, urls, model=None, local_ids=None, meta=None, select_classes=N
back in order).
meta: a string of any extra information to accompany the request. This has to be a string, so
if passing structured data, pass a json.dumps(meta) string.
select_classes: to select only a subset of all possible classes, enter a comma separated list
select_classes: to select only a subset of all possible classes, enter a comma separated list
of classes you want to predict. Ex: "dog,cat,tree,car,boat"
language: set the default language using it's two letter (with options -XX variant) ISO 639-1
code to use for all requests.
Expand Down Expand Up @@ -370,7 +406,7 @@ def tag_and_embed_urls(self, urls, model=None, local_ids=None, meta=None, select
back in order).
meta: a string of any extra information to accompany the request. This has to be a string, so
if passing structured data, pass a json.dumps(meta) string.
select_classes: to select only a subset of all possible classes, enter a comma separated list
select_classes: to select only a subset of all possible classes, enter a comma separated list
of classes you want to predict. Ex: "dog,cat,tree,car,boat"
language: set the default language using it's two letter (with options -XX variant) ISO 639-1
code to use for all requests.
Expand Down Expand Up @@ -560,7 +596,7 @@ def _setup_multi_data(self, ops, num_cases, model=None, local_ids=None, meta=Non
elif self._model: # use the variable passed into __init__
data['model'] = self._model
if language: # use the variable passed into method
data['language'] = self._sanitize_param(language, default=None)
data['language'] = self._parse_language(language)
elif self.language: # use the variable passed into __init__
data['language'] = self.language
if local_ids:
Expand Down
85 changes: 19 additions & 66 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,19 @@
import os
import hashlib
import unittest
from clarifai.client import ClarifaiApi, ApiClientError
from clarifai.client import ClarifaiApi

class TestClarifaiApi(unittest.TestCase):
"""
test the Clarifai API Python client with all supported features
"""
image_url = 'http://clarifai-img.s3.amazonaws.com/test/toddler-flowers.jpeg'
video_url = 'http://techslides.com/demos/sample-videos/small.mp4'

def get_client(self, *args, **kwargs):
return ClarifaiApi()

def test_api_connection(self):
api = self.get_client()
api = ClarifaiApi()
self.assertTrue(api)

def test_get_info(self):
api = self.get_client()
api = ClarifaiApi()
response = api.get_info()
self.assertTrue(response.get('api_version'))
self.assertTrue(len(response) > 0)
Expand All @@ -34,20 +29,14 @@ def test_tag_one_image(self):
""" tag one image, from url and disk """
# tag image from online URL
image_url = 'http://clarifai-img.s3.amazonaws.com/test/toddler-flowers.jpeg'
api = self.get_client()
api = ClarifaiApi()
response = api.tag_image_urls(image_url)
self.assertTrue(response)
result = response['results'][0]
self.assertTrue(result['url'] == image_url)

tag = result['result']['tag']
self.assertTrue('probs' in tag, 'probs not included in response')
self.assertTrue('classes' in tag, 'classes not included in response')
self.assertTrue(len(tag['probs']) == len(tag['classes']), 'lengths of tag fields did not match')
self.assertTrue(response['results'][0]['url'] == image_url)

# tag image from local fs
image_file = 'tests/data/toddler-flowers.jpeg'
api = self.get_client()
api = ClarifaiApi()
if os.path.exists(image_file):
with open(image_file, 'rb') as fb:
response = api.tag_images(fb)
Expand All @@ -60,15 +49,15 @@ def test_tag_images(self):
image_files = ['metro-north.jpg', 'octopus.jpg', 'tahoe.jpg', 'thai-market.jpg']
image_urls = [os.path.join(image_url_base, one_file) for one_file in image_files]

api = self.get_client()
api = ClarifaiApi()
response = api.tag_image_urls(image_urls)
self.assertTrue(response)

# tag images frmo local fs
image_dir = 'tests/data'
image_files = ['metro-north.jpg', 'octopus.jpg', 'tahoe.jpg', 'thai-market.jpg']

api = self.get_client()
api = ClarifaiApi()
if os.path.exists(image_dir):
image_files = [open(os.path.join(image_dir, one_file), 'rb') for one_file in image_files]
response = api.tag_images(image_files)
Expand All @@ -79,7 +68,7 @@ def test_tag_images(self):
def test_unicode_urls(self):
image_url = u'http://www.alvaronoboa.com/wp-content/uploads/2013/02/Álvaro-Noboa-y-Annabella-Azín-Votaciones-41-1024x682.jpg'

api = self.get_client()
api = ClarifaiApi()
response = api.tag_image_urls(image_url)
self.assertTrue(response)
self.assertTrue(response['results'][0]['url'] == image_url)
Expand All @@ -89,13 +78,13 @@ def test_tag_gif(self):
# source: http://media.giphy.com/media/fRZn2vraBGiA0/giphy.gif
image_url = 'http://media.giphy.com/media/fRZn2vraBGiA0/giphy.gif'

api = self.get_client()
api = ClarifaiApi()
response = api.tag_image_urls(image_url)
self.assertTrue(response)
self.assertTrue(response['results'][0]['url'] == image_url)

image_file = 'tests/data/water-ocean-turtle.gif'
api = self.get_client()
api = ClarifaiApi()
if os.path.exists(image_file):
with open(image_file, 'rb') as fb:
response = api.tag_images(fb)
Expand All @@ -105,30 +94,30 @@ def test_tag_one_video(self):
# video source: http://techslides.com/demos/sample-videos/small.mp4
video_url = 'http://techslides.com/demos/sample-videos/small.mp4'

api = self.get_client()
api = ClarifaiApi()
response = api.tag_image_urls(video_url)
self.assertTrue(response)
self.assertTrue(response['results'][0]['url'] == video_url)

def test_tag_one_video_from_localfs(self):
# video source: http://techslides.com/demos/sample-videos/small.mp4
video_file = 'tests/data/small.mp4'
api = self.get_client()
api = ClarifaiApi()
if os.path.exists(video_file):
with open(video_file, 'rb') as fb:
response = api.tag_images(fb)
self.assertTrue(response)

def test_embed_one_image(self):
image_url = 'http://clarifai-img.s3.amazonaws.com/test/toddler-flowers.jpeg'
api = self.get_client()
api = ClarifaiApi()
response = api.embed_image_urls(image_url)
self.assertTrue(response)
self.assertTrue(response['results'][0]['url'] == image_url)

def test_embed_one_image_from_localfs(self):
image_file = 'tests/data/toddler-flowers.jpeg'
api = self.get_client()
api = ClarifaiApi()
if os.path.exists(image_file):
with open(image_file, 'rb') as fb:
response = api.embed_images(fb)
Expand All @@ -139,15 +128,15 @@ def test_tag_n_embed_one_image(self):
image_files = ['metro-north.jpg', 'octopus.jpg', 'tahoe.jpg', 'thai-market.jpg']
image_urls = [os.path.join(image_url_base, one_file) for one_file in image_files]

api = self.get_client()
api = ClarifaiApi()
response = api.tag_and_embed_image_urls(image_urls)
self.assertTrue(response)

def test_tag_n_embed_from_localfs(self):
image_dir = 'tests/data'
image_files = ['metro-north.jpg', 'octopus.jpg', 'tahoe.jpg', 'thai-market.jpg']

api = self.get_client()
api = ClarifaiApi()
if os.path.exists(image_dir):
image_files = [open(os.path.join(image_dir, one_file), 'rb') for one_file in image_files]
response = api.tag_and_embed_images(image_files)
Expand All @@ -162,7 +151,7 @@ def test_send_feedback(self):
'http://clarifai-img.s3.amazonaws.com/test/metro-north.jpg', \
'http://clarifai-img.s3.amazonaws.com/test/octopus.jpg']

api = self.get_client()
api = ClarifaiApi()

response = api.feedback(urls=urls[0], add_tags='train')
self.assertTrue(response)
Expand All @@ -184,42 +173,6 @@ def test_send_feedback(self):
response = api.feedback(urls=urls, similar_docids=docids[:2], dissimilar_docids=docids[1:])
self.assertTrue(response)

def test_i18n(self):
with self.assertRaises(ApiClientError):
api = ClarifaiApi(language='aaa')

api = self.get_client()
request_body = api._setup_multi_data([], ['urls'], language='en')
self.assertEqual(request_body['language'], 'en', 'language field was not set')

languages = api.get_languages()
self.assertTrue(len(languages), 'did not return any languages')
self.assertTrue('en' in languages, 'english code not included in languages')

def test_concept_ids(self):
"""new models should return concept_ids"""
api = self.get_client()

api.set_model('general-v1.3')
response = api.tag_image_urls(self.image_url)
tag = response['results'][0]['result']['tag']
self.assertTrue('concept_ids' in tag, 'concept_ids not included in new model')
self.assertTrue(tag['concept_ids'][0].startswith('ai_'), 'concept id doesn\'t start with ai_')

response = api.tag_image_urls(self.video_url)
self.assertTrue(response['results'][0]['result']['tag']['concept_ids'][0][0].startswith('ai_'),
"video concept_ids didn't start wit ai_")


api.set_model('general-v1.1')
response = api.tag_image_urls(self.image_url)
tag = response['results'][0]['result']['tag']
self.assertTrue('concept_ids' not in tag, 'concept_ids included in old model')

response = api.tag_image_urls(self.video_url)
tag = response['results'][0]['result']['tag']
self.assertTrue('concept_ids' not in tag, 'concept_ids included in old model')


if __name__ == '__main__':
unittest.main()