diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f448e31 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build +dist +face_client.egg-info \ No newline at end of file diff --git a/CHANGES b/CHANGES index e145281..b624ffc 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,15 @@ -1.3 - in development: +1.4 - 12.11.2021: + + - Added Multipart request Python 3.X compatibility + +1.3 - 02.02.2013: - Allow user to pass file-like object for the 'file' argument to the faces_detect and faces_recognize function. [Sebastian Noack] + - Switched to SkyBiometry Face Detection and Recognition API. + - Added possibility to pass image file content for the 'buffer' + argument to faces_detect and faces_recognize function. 1.2 - 25.09.2011: diff --git a/EXAMPLE.rst b/EXAMPLE.rst deleted file mode 100644 index 35d861d..0000000 --- a/EXAMPLE.rst +++ /dev/null @@ -1,106 +0,0 @@ -EXAMPLE -======= - -Here is a short example demonstrating how you can use this client. - -Lets say that we want to create our own private namespace and train it to recognize Guido Van Rossum. - -Here are the images which we will use for training our namespace index: - -| http://savasplace.com/wp-content/uploads/2009/04/guido-van-rossum.jpg -| http://farm1.static.flickr.com/43/104506247_c748f20b83.jpg -| http://farm1.static.flickr.com/67/200126290_2798330e61.jpg - -And here is the image which hopefully, after training our index will be recognized as "Guido Van Rossum": - -http://farm1.static.flickr.com/41/104498903_bad315cee0.jpg - -#. First we create our private namespace named **testns** (this can be done on the `face.com page`_) - -#. Now we import the module and instantiate the class with our face.com **api_key** and **api_secret** (you can get them by registering your application on `face.com page`_):: - - >> from face_client import FaceClient - >> client = FaceClient('API_KEY', 'API_SECRET') - -#. Before training our namespace index I just want to show you that the image is not already recognized:: - - >> client.faces_recognize('guido', 'http://farm1.static.flickr.com/41/104498903_bad315cee0.jpg', namespace = 'testns') - - {u'no_training_set': [u'guido@testns'], - u'photos': [{u'height': 375, - ...omitted for clarity... - u'tid': u'TEMP_F@51b67ae268617da2c99c69091ab8f3b0_cf224a584e806722a7fa15a936ed1367_48.00_41.82_0', - u'uids': [], - u'width': 47, - u'yaw': 31.649999999999999}], - u'url': u'http://farm1.static.flickr.com/41/104498903_bad315cee0.jpg', - u'width': 500}], - ...omitted for clarity... - - As you can see, the "uids" list is empty, meaning that Guido Van Rossum is not yet recognized in our **testns** namespace. - -#. Saving the tags and training our index - - For saving the tags, we need to provide the **tags_save** method with the tag ids, which we can obtain by using the **faces_detect** or **faces_recognize** method. - - In this example, I will use faces_detect:: - - >> response = client.faces_detect('http://savasplace.com/wp-content/uploads/2009/04/guido-van-rossum.jpg,http://farm1.static.flickr.com/43/104506247_c748f20b83.jpg,http://farm1.static.flickr.com/67/200126290_2798330e61.jpg') - >> tids = [photo['tags'][0]['tid'] for photo in response['photos']] - - >> tids - - [u'TEMP_F@cc96b0429a7946711de5693c5ff67c46_cf224a584e80672ea7fa15a936ed1367_47.00_27.83_0', - u'TEMP_F@e2ee88f20076bc1a60c3629281f34197_cf224a584e80672ea7fa15a936ed1367_48.00_34.93_0', - u'TEMP_F@33c91a546bbba775628e7d7ca969f7ce_cf224a584e80672ea7fa15a936ed1367_48.35_26.40_0'] - - We can also check that the tags were saved by using the **tags_get** method:: - - >> client.tags_get('guido@testns') - - {u'message': u'Tags saved with uid: guido@testns ,label: Guido Van Rossum', - u'saved_tags': [{u'detected_tid': u'TEMP_F@cc96b0429a7946711de5693c5ff67c46_cf224a584e80672ea7fa15a936ed1367_47.00_27.83_0', - u'tid': u'21319_cf224a584e80672ea7fa15a936ed1367'}, - {u'detected_tid': u'TEMP_F@e2ee88f20076bc1a60c3629281f34197_cf224a584e80672ea7fa15a936ed1367_48.00_34.93_0', - u'tid': u'21321_cf224a584e80672ea7fa15a936ed1367'}, - {u'detected_tid': u'TEMP_F@33c91a546bbba775628e7d7ca969f7ce_cf224a584e80672ea7fa15a936ed1367_48.35_26.40_0', - u'tid': u'21323_cf224a584e80672ea7fa15a936ed1367'}], - u'status': u'success'} - -#. Now when we have the temporary tag ids, we can use them save to save the tags and train our namespace index:: - - >> client.tags_save(tids = ',' . join(tids), uid = 'guido@testns', label = 'Guido Van Rossum') - >> client.faces_train('guido@testns') - - {u'status': u'success', - u'unchanged': [{u'last_trained': 1274462404, - u'training_in_progress': False, - u'training_set_size': 3, - u'uid': u'guido@testns'}]} - -#. Now after we have trained our index, lets check if Guido is recognized:: - - >> client.faces_recognize('all', 'http://farm1.static.flickr.com/41/104498903_bad315cee0.jpg', namespace = 'testns') - - {u'photos': [{u'height': 375, - u'pid': u'F@2981c22e78cc0f12276825aa0b05df86_cf224a584e80672ea7fa15a936ed1367', - ...omitted for clarity... - u'roll': -1.3400000000000001, - u'tagger_id': None, - u'threshold': 60, - u'tid': u'TEMP_F@2981c22e78cc0f12276825aa0b05df86_cf224a584e80672ea7fa15a936ed1367_51.00_35.20_2', - u'uids': [{u'confidence': 20, - u'uid': u'guido@testns'}], - u'width': 18.600000000000001, - u'yaw': 36}], - u'url': u'http://farm1.static.flickr.com/41/104498903_bad315cee0.jpg', - u'width': 500}], - u'status': u'success', - ...omitted for clarity... - - As you can see by looking at the uids key, Guido was now recognized with a 20% confidence! - -For more information about the face.com API and how to use it with Facebook and Twitter, visit the `official documentation`_. - -.. _face.com page: http://developers.face.com/account/ -.. _official documentation: http://developers.face.com/docs/recognition-howto/ diff --git a/README.rst b/README.rst index 3661b1e..a0ed59c 100644 --- a/README.rst +++ b/README.rst @@ -1,24 +1,170 @@ -face.com Python API client library -================================== +SkyBiometry Face Detection and Recognition API client library +============================================================= -face.com_ REST API Python client library. - -For a demonstration how to use this library, see EXAMPLE.RST. +SkyBiometry Face Detection and Recognition REST API client library. For more information about the API and the return values, visit the `official documentation`_. -Performing actions involving Facebook or Twitter users -====================================================== +Example +------- + +Here is a short example demonstrating how you can use this client. + +Lets say that we want to create our own private namespace and train it to recognize Guido Van Rossum. + +Here are the images which we will use for training our namespace index: + +| http://savasplace.com/wp-content/uploads/2009/04/guido-van-rossum.jpg +| http://farm1.static.flickr.com/43/104506247_c748f20b83.jpg +| http://farm1.static.flickr.com/67/200126290_2798330e61.jpg + +And here is the image which hopefully, after training our index will be recognized as "Guido Van Rossum": + +http://farm1.static.flickr.com/41/104498903_bad315cee0.jpg + +#. First we create our private namespace named **testns** (this can be done on the `SkyBiometry page`_). + +#. Now we import the module and instantiate the class with our SkyBiometry **API_KEY** and **API_SECRET** (you can get them by registering your application on `SkyBiometry page`_):: + + >> from face_client import FaceClient + >> client = FaceClient('API_KEY', 'API_SECRET') + +#. Before training our namespace index I just want to show you that the image is not already recognized:: + + >> client.faces_recognize('guido', 'http://farm1.static.flickr.com/41/104498903_bad315cee0.jpg', namespace = 'testns') + + { + u'status': u'success', + u'photos': [{ + u'url': u'http://farm1.static.flickr.com/41/104498903_bad315cee0.jpg', + u'width': 500, + u'tags': [{ + u'eye_left': {u'y': 31.2, u'x': 55.6}, + u'confirmed': False, + u'uids': [], + u'yaw': -45, + u'manual': False, + u'height': 18.13, + u'width': 13.6, + u'mouth_center': {u'y': 43.47, u'x': 52.6}, + u'nose': {u'y': 36.53, u'x': 53.4}, + u'eye_right': {u'y': 30.93, u'x': 48.0}, + u'pitch': 0, + u'tid': u'TEMP_F@08e31221350a43d267be01d500f10086_1d12ece6a6ea2_48.20_35.73_0_1', + u'attributes': { + u'gender': {u'confidence': 47, u'value': u'male'}, + u'smiling': {u'confidence': 85, u'value': u'false'}, + u'glasses': {u'confidence': 27, u'value': u'false'}, + u'dark_glasses': {u'confidence': 89, u'value': u'false'}, + u'face': {u'confidence': 71, u'value': u'true'} + }, + u'recognizable': True, + u'roll': 3, + u'center': {u'y': 35.73, u'x': 48.2} + }], + u'pid': u'F@08e31221350a43d267be01d572dc824b_1d12ece6a6ea2', + u'height': 375 + }], + u'usage': {...omitted for clarity...} + } + + As you can see, the "uids" list is empty, meaning that Guido Van Rossum is not yet recognized in our **testns** namespace. + +#. Saving the tags and training our index. For saving the tags, we need to provide the **tags_save** method with the tag ids, which we are obtained by using the **faces_detect** or **faces_recognize** method. In this example, I will use **faces_detect**:: + + >> response = client.faces_detect('http://savasplace.com/wp-content/uploads/2009/04/guido-van-rossum.jpg,http://farm1.static.flickr.com/43/104506247_c748f20b83.jpg,http://farm1.static.flickr.com/67/200126290_2798330e61.jpg') + >> tids = [photo['tags'][0]['tid'] for photo in response['photos']] + >> tids + + [ + u'TEMP_F@0bf0294f6c43162105c9bdfa00bc00ab_15e78870a332a_47.00_28.50_0_1', + u'TEMP_F@008f7f3d4f93956f2fd24b1e01000084_e29f2ba8f58c6_51.20_35.20_0_1', + u'TEMP_F@0d38a4e97c5c63042b5da6da00a10088_73a8fb3908097_48.35_27.20_0_1' + ] + +#. Now when we have the temporary tag ids, we can use them to save the tags and train our namespace index:: + + >> client.tags_save(tids = ',' . join(tids), uid = 'guido@testns', label = 'Guido Van Rossum') + + { + u'status': u'success', + u'message': u'Tags saved with uid: guido@testns, label: Guido Van Rossum', + u'saved_tags': [ + {u'tid': u'00bc00ab_15e78870a332a', u'detected_tid': u'TEMP_F@0bf0294f6c43162105c9bdfa00bc00ab_15e78870a332a_47.00_28.50_0_1'}, + {u'tid': u'01000084_e29f2ba8f58c6', u'detected_tid': u'TEMP_F@008f7f3d4f93956f2fd24b1e01000084_e29f2ba8f58c6_51.20_35.20_0_1'}, + {u'tid': u'00a10088_73a8fb3908097', u'detected_tid': u'TEMP_F@0d38a4e97c5c63042b5da6da00a10088_73a8fb3908097_48.35_27.20_0_1'} + ] + } + + >> client.faces_train('guido@testns') + + { + u'status': u'success', + u'created': [{ + u'training_set_size': 3, + u'last_trained': 1361651583, + u'uid': u'guido@testns', + u'training_in_progress': False} + ] + } + +#. We can also check that the tags were saved by using the **tags_get** method:: + + >> client.tags_get('guido@testns') + + { + u'status': u'success', + u'photos': [ + {u'url': u'http://farm1.static.flickr.com/67/200126290_2798330e61.jpg', ...omitted for clarity...}, + {u'url': u'http://farm1.static.flickr.com/43/104506247_c748f20b83.jpg', ...omitted for clarity...}, + {u'url': u'http://savasplace.com/wp-content/uploads/2009/04/guido-van-rossum.jpg', ...omitted for clarity...} + ], + u'usage': {...omitted for clarity...} + } -If you want to perform actions involving Facebook or Twitter users you need to provide the necessary credentials. +#. Now after we have trained our index, lets check if Guido is recognized:: -#. **Facebook**:: + >> client.faces_recognize('all', 'http://farm1.static.flickr.com/41/104498903_bad315cee0.jpg', namespace = 'testns') - client.set_facebook_oauth_credentials('FB_USER_ID', 'FB_SESSION_ID', 'FB_OAUTH_TOKEN') + { + u'status': u'success', + u'photos': [{ + u'url': u'http://farm1.static.flickr.com/41/104498903_bad315cee0.jpg', + u'width': 500, + u'tags': [{ + u'eye_left': {u'y': 31.2, u'x': 55.6}, + u'confirmed': False, + u'uids': [{u'confidence': 34, u'uid': u'guido@testns'}], + u'width': 13.6, + u'yaw': -45, + u'manual': False, + u'height': 18.13, + u'threshold': 30, + u'mouth_center': {u'y': 43.47, u'x': 52.6}, + u'nose': {u'y': 36.53, u'x': 53.4}, + u'eye_right': {u'y': 30.93, u'x': 48.0}, + u'pitch': 0, + u'tid': u'TEMP_F@08e31221350a43d267be01d500f10086_1d12ece6a6ea2_48.20_35.73_0_1', + u'attributes': { + u'gender': {u'confidence': 47, u'value': u'male'}, + u'smiling': {u'confidence': 85, u'value': u'false'}, + u'glasses': {u'confidence': 27, u'value': u'false'}, + u'dark_glasses': {u'confidence': 89, u'value': u'false'}, + u'face': {u'confidence': 71, u'value': u'true'} + }, + u'recognizable': True, + u'roll': 3, + u'center': {u'y': 35.73, u'x': 48.2} + }], + u'pid': u'F@08e31221350a43d267be01d572dc824b_1d12ece6a6ea2', + u'height': 375 + }], + u'usage': {...omitted for clarity...} + } -#. **Twitter (OAuth)**:: + As you can see by looking at the "uids" list, Guido was now recognized with a 34% confidence! - client.set_twitter_oauth_credentials('OAUTH_USER', 'OAUTH_SECRET', 'OAUTH_TOKEN') +For more information about the SkyBiometry Face Detection and Recognition API and how to use it, visit the `official documentation`_. -.. _face.com: http://developers.face.com/ -.. _official documentation: http://developers.face.com/docs/api/ +.. _SkyBiometry page: http://www.skybiometry.com/Account +.. _official documentation: http://www.skybiometry.com/Documentation diff --git a/face_client/__init__.py b/face_client/__init__.py index 33b5cb5..e3248fd 100644 --- a/face_client/__init__.py +++ b/face_client/__init__.py @@ -1,3 +1,3 @@ -__version__ = (1, 2, 'dev') +__version__ = (1, 4, 'dev') from face_client import * diff --git a/face_client/face_client.py b/face_client/face_client.py index 27e9269..dc9292d 100644 --- a/face_client/face_client.py +++ b/face_client/face_client.py @@ -1,28 +1,33 @@ # -*- coding: utf-8 -*- # -# Name: face.com Python API client library -# Description: face.com REST API Python client library. +# Name: SkyBiometry Face Detection and Recognition API Python client library +# Description: SkyBiometry Face Detection and Recognition REST API Python client library. # # For more information about the API and the return values, -# visit the official documentation at http://developers.face.com/docs/api/. +# visit the official documentation at http://www.skybiometry.com/Documentation # # Author: Tomaž Muraus (http://www.tomaz.me) # License: BSD -import urllib -import urllib2 import os.path import warnings +import requests +try: + from urllib.parse import urlencode +except ImportError: + from urllib import urlencode + try: import json except ImportError: import simplejson as json + +from future.utils import iteritems -API_HOST = 'api.face.com' +API_HOST = 'api.skybiometry.com/fc' USE_SSL = True - class FaceClient(object): def __init__(self, api_key=None, api_secret=None): if not api_key or not api_secret: @@ -37,11 +42,10 @@ def __init__(self, api_key=None, api_secret=None): def set_twitter_user_credentials(self, *args, **kwargs): warnings.warn(('Twitter username & password auth has been ' + - 'deprecated. Please use oauth based auth - ' + - 'set_twitter_oauth_credentials()')) + 'deprecated. Please use oauth based auth - ' + + 'set_twitter_oauth_credentials()')) - def set_twitter_oauth_credentials(self, user=None, secret=None, - token=None): + def set_twitter_oauth_credentials(self, user=None, secret=None, token=None): if not user or not secret or not token: raise AttributeError('Missing one of the required arguments') @@ -51,14 +55,12 @@ def set_twitter_oauth_credentials(self, user=None, secret=None, def set_facebook_access_token(self, *args, **kwargs): warnings.warn(('Method has been renamed to ' + - ' set_facebook_oauth_credentials(). Support for' + - 'username & password based auth has also been dropped.' + - 'Now only oAuth2 token based auth is supported')) - - def set_facebook_oauth_credentials(self, user_id=None, session_id=None, - oauth_token=None): - for (key, value) in [('user_id', user_id), ('session_id', session_id), - ('oauth_token', oauth_token)]: + 'set_facebook_oauth_credentials(). Support for ' + + 'username & password based auth has also been dropped. ' + + 'Now only oAuth2 token based auth is supported')) + + def set_facebook_oauth_credentials(self, user_id=None, session_id=None, oauth_token=None): + for (key, value) in [('user_id', user_id), ('session_id', session_id), ('oauth_token', oauth_token)]: if not value: raise AttributeError('Missing required argument: %s' % (key)) @@ -67,19 +69,20 @@ def set_facebook_oauth_credentials(self, user_id=None, session_id=None, 'fb_oauth_token': oauth_token} ### Recognition engine methods ### - def faces_detect(self, urls=None, file=None, aggressive=False): + def faces_detect(self, urls=None, file=None, buffer=None, aggressive=False): """ Returns tags for detected faces in one or more photos, with geometric information of the tag, eyes, nose and mouth, as well as the gender, glasses, and smiling attributes. - http://developers.face.com/docs/api/faces-detect/ + http://www.skybiometry.com/Documentation#faces/detect """ - if not urls and not file: - raise AttributeError('Missing URLs/filename argument') + if not urls and not file and not buffer: + raise AttributeError('Missing URLs/filename/buffer argument') - data = {'attributes': 'all'} + data = {'attributes': 'all', 'force_reprocess_image': 'true'} files = [] + buffers = [] if file: # Check if the file exists @@ -87,26 +90,27 @@ def faces_detect(self, urls=None, file=None, aggressive=False): raise IOError('File %s does not exist' % (file)) files.append(file) + elif buffer: + buffers.append(buffer) else: data['urls'] = urls if aggressive: - data['detector'] = 'Aggressive' + data['detector'] = 'aggressive' - response = self.send_request('faces/detect', data, files) + response = self.send_request('faces/detect', data, files, buffers) return response def faces_status(self, uids=None, namespace=None): """ Reports training set status for the specified UIDs. - http://developers.face.com/docs/api/faces-status/ + http://www.skybiometry.com/Documentation#faces/status """ if not uids: raise AttributeError('Missing user IDs') - (facebook_uids, twitter_uids) = \ - self.__check_user_auth_credentials(uids) + (facebook_uids, twitter_uids) = self.__check_user_auth_credentials(uids) data = {'uids': uids} self.__append_user_auth_data(data, facebook_uids, twitter_uids) @@ -115,41 +119,43 @@ def faces_status(self, uids=None, namespace=None): response = self.send_request('faces/status', data) return response - def faces_recognize(self, uids=None, urls=None, file=None, train=None, - namespace=None): + def faces_recognize(self, uids=None, urls=None, file=None, buffer=None, aggressive=False, train=None, namespace=None): """ Attempts to detect and recognize one or more user IDs' faces, in one or more photos. - For each detected face, the face.com engine will return the most likely + For each detected face, the SkyBiometry engine will return the most likely user IDs, or empty result for unrecognized faces. In addition, each tag includes a threshold score - any score below this number is considered a low-probability hit. - http://developers.face.com/docs/api/faces-recognize/ + http://www.skybiometry.com/Documentation#faces/recognize """ - if not uids or (not urls and not file): + if not uids or (not urls and not file and not buffer): raise AttributeError('Missing required arguments') - (facebook_uids, twitter_uids) = \ - self.__check_user_auth_credentials(uids) + (facebook_uids, twitter_uids) = self.__check_user_auth_credentials(uids) data = {'uids': uids, 'attributes': 'all'} files = [] + buffers = [] if file: # Check if the file exists if not hasattr(file, 'read') and not os.path.exists(file): raise IOError('File %s does not exist' % (file)) - files.append(file) + elif buffer: + buffers.append(buffer) else: data.update({'urls': urls}) + if aggressive: + data['detector'] = 'aggressive' + self.__append_user_auth_data(data, facebook_uids, twitter_uids) - self.__append_optional_arguments(data, train=train, - namespace=namespace) + self.__append_optional_arguments(data, train=train, namespace=namespace) - response = self.send_request('faces/recognize', data, files) + response = self.send_request('faces/recognize', data, files, buffers) return response def faces_train(self, uids=None, namespace=None): @@ -157,13 +163,12 @@ def faces_train(self, uids=None, namespace=None): Calls the training procedure for the specified UIDs, and reports back changes. - http://developers.face.com/docs/api/faces-train/ + http://www.skybiometry.com/Documentation#faces/train """ if not uids: raise AttributeError('Missing user IDs') - (facebook_uids, twitter_uids) = \ - self.__check_user_auth_credentials(uids) + (facebook_uids, twitter_uids) = self.__check_user_auth_credentials(uids) data = {'uids': uids} self.__append_user_auth_data(data, facebook_uids, twitter_uids) @@ -173,8 +178,7 @@ def faces_train(self, uids=None, namespace=None): return response ### Methods for managing face tags ### - def tags_get(self, uids=None, urls=None, pids=None, order='recent', \ - limit=5, together=False, filter=None, namespace=None): + def tags_get(self, uids=None, urls=None, pids=None, order='recent', limit=5, together=False, filter=None, namespace=None): """ Returns saved tags in one or more photos, or for the specified User ID(s). @@ -182,29 +186,25 @@ def tags_get(self, uids=None, urls=None, pids=None, order='recent', \ corresponding to a more specific criteria such as front-facing, recent, or where two or more users appear together in same photos. - http://developers.face.com/docs/api/tags-get/ + http://www.skybiometry.com/Documentation#tags/get """ - (facebook_uids, twitter_uids) = \ - self.__check_user_auth_credentials(uids) + if not uids and not urls: + raise AttributeError('Missing user IDs or URLs') + (facebook_uids, twitter_uids) = self.__check_user_auth_credentials(uids) - data = {'uids': uids, - 'urls': urls, - 'together': together, - 'limit': limit} + data = { 'together': 'true' if together else 'false', 'limit': limit } self.__append_user_auth_data(data, facebook_uids, twitter_uids) - self.__append_optional_arguments(data, pids=pids, filter=filter, - namespace=namespace) + self.__append_optional_arguments(data, uids=uids, urls=urls, pids=pids, filter=filter, namespace=namespace) response = self.send_request('tags/get', data) return response - def tags_add(self, url=None, x=None, y=None, width=None, uid=None, - tagger_id=None, label=None, password=None): + def tags_add(self, url=None, x=None, y=None, width=None, uid=None, tagger_id=None, label=None, password=None): """ Add a (manual) face tag to a photo. Use this method to add face tags where those were not detected for completeness of your service. - http://developers.face.com/docs/api/tags-add/ + http://www.skybiometry.com/Documentation#tags/add """ if not url or not x or not y or not width or not uid or not tagger_id: raise AttributeError('Missing one of the required arguments') @@ -223,14 +223,12 @@ def tags_add(self, url=None, x=None, y=None, width=None, uid=None, response = self.send_request('tags/add', data) return response - def tags_save(self, tids=None, uid=None, tagger_id=None, label=None, \ - password=None): + def tags_save(self, tids=None, uid=None, tagger_id=None, label=None, password=None): """ Saves a face tag. Use this method to save tags for training the - face.com index, or for future use of the faces.detect and tags.get - methods. + index, or for future use of the faces.detect and tags.get methods. - http://developers.face.com/docs/api/tags-save/ + http://www.skybiometry.com/Documentation#tags/save """ if not tids or not uid: raise AttributeError('Missing required argument') @@ -240,8 +238,7 @@ def tags_save(self, tids=None, uid=None, tagger_id=None, label=None, \ data = {'tids': tids, 'uid': uid} self.__append_user_auth_data(data, facebook_uids, twitter_uids) - self.__append_optional_arguments(data, tagger_id=tagger_id, - label=label, password=password) + self.__append_optional_arguments(data, tagger_id=tagger_id, label=label, password=password) response = self.send_request('tags/save', data) return response @@ -250,7 +247,7 @@ def tags_remove(self, tids=None, password=None): """ Remove a previously saved face tag from a photo. - http://developers.face.com/docs/api/tags-remove/ + http://www.skybiometry.com/Documentation#tags/remove """ if not tids: raise AttributeError('Missing tag IDs') @@ -266,7 +263,7 @@ def account_limits(self): Returns current rate limits for the account represented by the passed API key and Secret. - http://developers.face.com/docs/api/account-limits/ + http://www.skybiometry.com/Documentation#account/limits """ response = self.send_request('account/limits') return response['usage'] @@ -276,30 +273,38 @@ def account_users(self, namespaces=None): Returns current rate limits for the account represented by the passed API key and Secret. - http://developers.face.com/docs/api/account-limits/ + http://www.skybiometry.com/Documentation#account/users """ if not namespaces: raise AttributeError('Missing namespaces argument') - response = self.send_request('account/users', - {'namespaces': namespaces}) + response = self.send_request('account/users', { 'namespaces': namespaces }) + + return response + + def account_namespaces(self): + """ + Returns all valid data namespaces for user authorized by specified API key. + + http://www.skybiometry.com/Documentation#account/namespaces + """ + + response = self.send_request('account/namespaces') return response def __check_user_auth_credentials(self, uids): # Check if needed credentials are provided - facebook_uids = [uid for uid in uids.split(',') \ - if uid.find('@facebook.com') != -1] - twitter_uids = [uid for uid in uids.split(',') \ - if uid.find('@twitter.com') != -1] + facebook_uids = [uid for uid in uids.split(',') if uid.find('@facebook.com') != -1] + twitter_uids = [uid for uid in uids.split(',') if uid.find('@twitter.com') != -1] if facebook_uids and not self.facebook_credentials: raise AttributeError('You need to set Facebook credentials ' + - 'to perform action on Facebook users') + 'to perform action on Facebook users') if twitter_uids and not self.twitter_credentials: raise AttributeError('You need to set Twitter credentials to ' + - 'perform action on Twitter users') + 'perform action on Twitter users') return (facebook_uids, twitter_uids) @@ -320,66 +325,66 @@ def __append_user_auth_data(self, data, facebook_uids, twitter_uids): self.twitter_credentials['twitter_oauth_token']))}) def __append_optional_arguments(self, data, **kwargs): - for key, value in kwargs.iteritems(): + for key, value in iteritems(kwargs): if value: data.update({key: value}) - def send_request(self, method=None, parameters=None, files=None): - if USE_SSL: - protocol = 'https://' - else: - protocol = 'http://' - - url = '%s%s/%s' % (protocol, API_HOST, method) - - data = {'api_key': self.api_key, - 'api_secret': self.api_secret, - 'format': self.format} + def send_request(self, method=None, parameters=None, files=None, buffers=None): + protocol = 'https://' if USE_SSL else 'http://' + url = '%s%s/%s.%s' % (protocol, API_HOST, method, self.format) + data = { 'api_key': self.api_key, 'api_secret': self.api_secret } if parameters: data.update(parameters) # Local file is provided, use multi-part form - if files: + if files or buffers: from multipart import Multipart form = Multipart() - for key, value in data.iteritems(): + for key, value in iteritems (data): form.field(key, value) - for i, file in enumerate(files, 1): - if hasattr(file, 'read'): - if hasattr(file, 'name'): - name = os.path.basename(file.name) + if files: + for i, file in enumerate(files, 1): + if hasattr(file, 'read'): + if hasattr(file, 'name'): + name = os.path.basename(file.name) + else: + name = 'attachment_%d' % i + close_file = False else: - name = 'attachment_%d' % i - close_file = False - else: - name = os.path.basename(file) - file = open(file, 'r') - close_file = True - - try: - form.file(name, name, file.read()) - finally: - if close_file: - file.close() - + name = os.path.basename(file) + file = open(file, 'rb') + close_file = True + + try: + form.file(name, name, file.read()) + finally: + if close_file: + file.close() + else: + for i, buffer in enumerate(buffers, 1): + name = 'attachment_%d' % i + form.file(name, name, buffer) (content_type, post_data) = form.get() headers = {'Content-Type': content_type} else: - post_data = urllib.urlencode(data) + print(data) + post_data = urlencode(data) headers = {} - request = urllib2.Request(url, headers=headers, data=post_data) - response = urllib2.urlopen(request) - response = response.read() + + try: + r = requests.post(url, headers=headers, data=post_data) + response = r.text + except HTTPError as e: + response = e.response.text + response_data = json.loads(response) - if 'status' in response_data and \ - response_data['status'] == 'failure': - raise FaceError(response_data['error_code'], - response_data['error_message']) + if 'status' in response_data and response_data['status'] == 'failure': + raise FaceError(response_data['error_code'], response_data['error_message']) return response_data @@ -391,3 +396,4 @@ def __init__(self, error_code, error_message): def __str__(self): return '%s (%d)' % (self.error_message, self.error_code) + diff --git a/face_client/multipart.py b/face_client/multipart.py index 836b968..31006ff 100644 --- a/face_client/multipart.py +++ b/face_client/multipart.py @@ -45,7 +45,14 @@ def __init__(self, name, filename, body, headers): self._headers = headers.copy() self._name = name self._filename = filename - self._body = body + + # If body is a byte string - decode it to a regular string. + # Required for Python 3.X compatibility as string handling has changed. + if filename and 'b\'' in str(body): + self._body = body.decode('latin') + else: + self._body = body + # We respect any content type passed in, but otherwise set it here. # We set the content disposition now, overwriting any prior value. if self._filename == None: diff --git a/setup.py b/setup.py index 89a1d65..8e13ea2 100644 --- a/setup.py +++ b/setup.py @@ -1,44 +1,47 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/env python -import os -import re -from distutils.core import setup - -version_re = re.compile( - r'__version__ = (\(.*?\))') - -cwd = os.path.dirname(os.path.abspath(__file__)) -fp = open(os.path.join(cwd, 'face_client', '__init__.py')) - -version = None -for line in fp: - match = version_re.search(line) - if match: - version = eval(match.group(1)) - break -else: - raise Exception('Cannot find version in __init__.py') -fp.close() - -setup(name='face_client', - version='.' . join(map(str, version)), - description='face.com face recognition Python API client library', - author='Tomaž Muraus', - author_email='tomaz@tomaz.me', - license='BSD', - url='http://github.com/Kami/python-face-client', - download_url='http://github.com/Kami/python-face-client/', - packages=['face_client'], - provides=['face_client'], - - classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Security', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], -) +# -*- coding: utf-8 -*- +#!/usr/bin/env python +import os +import re +from setuptools import setup + +version_re = re.compile( + r'__version__ = (\(.*?\))') + +cwd = os.path.dirname(os.path.abspath(__file__)) +fp = open(os.path.join(cwd, 'face_client', '__init__.py')) + +version = None +for line in fp: + match = version_re.search(line) + if match: + version = eval(match.group(1)) + break +else: + raise Exception('Cannot find version in __init__.py') +fp.close() + +setup(name='face_client', + version='.' . join(map(str, version)), + description='SkyBiometry Face Detection and Recognition API Python client library', + author='Tomaž Muraus', + author_email='tomaz@tomaz.me', + license='BSD', + url='http://github.com/Liuftvafas/python-face-client', + download_url='http://github.com/Liuftvafas/python-face-client/', + packages=['face_client'], + provides=['face_client'], + install_requires=[ + 'requests', + 'future' + ], + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Security', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], +)