diff --git a/pyfb/client.py b/pyfb/client.py index 8495911..779c6ab 100755 --- a/pyfb/client.py +++ b/pyfb/client.py @@ -2,94 +2,88 @@ The implementation of the Facebook Client """ -import urllib -import auth from urlparse import parse_qsl from utils import Json2ObjectsFactory +import auth +import requests +import urllib +import urllib2 class FacebookClient(object): """ This class implements the interface to the Facebook Graph API """ - FACEBOOK_URL = "https://www.facebook.com/" - GRAPH_URL = "https://graph.facebook.com/" - API_URL = "https://api.facebook.com/" - - BASE_AUTH_URL = "%soauth/authorize?" % GRAPH_URL - DIALOG_BASE_URL = "%sdialog/feed?" % FACEBOOK_URL - FBQL_BASE_URL = "%smethod/fql.query?" % API_URL - BASE_TOKEN_URL = "%soauth/access_token?" % GRAPH_URL + FACEBOOK_DOMAIN = "www.facebook.com" + GRAPH_DOMAIN = "graph.facebook.com" + API_DOMAIN = "api.facebook.com" DEFAULT_REDIRECT_URI = "http://www.facebook.com/connect/login_success.html" DEFAULT_SCOPE = auth.ALL_PERMISSIONS DEFAULT_DIALOG_URI = "http://www.example.com/response/" - #A factory to make objects from a json + # A factory to make objects from a json factory = Json2ObjectsFactory() - def __init__(self, app_id, access_token=None, raw_data=None): - + def __init__(self, app_id, access_token=None, timeout=30): self.app_id = app_id self.access_token = access_token - self.raw_data = raw_data self.permissions = self.DEFAULT_SCOPE self.expires = None - def _make_request(self, url, **data): - """ - Makes a simple request. If not data is a GET else is a POST. - """ - if not data: - data = None - return urllib.urlopen(url, data).read() + self.session = requests.Session() + self.session.timeout = timeout - def _make_auth_request(self, path, **data): + def _make_request(self, method="get", domain=GRAPH_DOMAIN, path=None, params=None, auth=True, **data): """ Makes a request to the facebook Graph API. This method requires authentication! Don't forgot to get the access token before use it. """ - if self.access_token is None: - raise PyfbException("Must Be authenticated. Do you forgot to get the access token?") - - token_url = "?access_token=%s" % self.access_token - url = "%s%s%s" % (self.GRAPH_URL, path, token_url) - if data: - post_data = urllib.urlencode(data) + if params is None: + params = {} else: - post_data = None - return urllib.urlopen(url, post_data).read() + for key, value in params.items(): + if value is None: + del params[key] - def _make_object(self, name, data): - """ - Uses the factory to make an object from a json - """ - if not self.raw_data: - return self.factory.make_object(name, data) - return self.factory.loads(data) + if auth: + if self.access_token is None: + raise PyfbException("Must Be authenticated. Do you forgot to get the access token?") + + params["access_token"] = self.access_token - def _get_url_path(self, dic): + url = "https://%s/%s" % (domain, path) - return urllib.urlencode(dic) + response = self.session.request(method, url, params, data) + if response.status_code < 200 or response.status_code > 299: + if response.content: + content = self.factory.make_object("Error", response.content) + + if hasattr(content, "error"): + error = content.error + + raise PyfbStatusException(error.type, error.code, getattr(error, "error_subcode", None), error.message) + + raise PyfbException(response.content) + + return response.content + + def _build_url(self, domain, path, params): + return "https://%s/%s?%s" % (domain, path, urllib.urlencode(params)) def _get_auth_url(self, params, redirect_uri): """ Returns the authentication url """ - if redirect_uri is None: - redirect_uri = self.DEFAULT_REDIRECT_URI params['redirect_uri'] = redirect_uri - url_path = self._get_url_path(params) - url = "%s%s" % (self.BASE_AUTH_URL, url_path) - return url + return self._build_url(self.FACEBOOK_DOMAIN, "oauth/authorize", params) def _get_permissions(self): - return ",".join(self.permissions) - def get_auth_token_url(self, redirect_uri): + def get_auth_token_url(self, redirect_uri=DEFAULT_REDIRECT_URI): """ Returns the authentication token url """ @@ -98,9 +92,10 @@ def get_auth_token_url(self, redirect_uri): "type": "user_agent", "scope": self._get_permissions(), } + return self._get_auth_url(params, redirect_uri) - def get_auth_code_url(self, redirect_uri): + def get_auth_code_url(self, redirect_uri=DEFAULT_REDIRECT_URI): """ Returns the url to get a authentication code """ @@ -108,59 +103,62 @@ def get_auth_code_url(self, redirect_uri): "client_id": self.app_id, "scope": self._get_permissions(), } - return self._get_auth_url(params, redirect_uri) - - def get_access_token(self, app_secret_key, secret_code, redirect_uri): - if redirect_uri is None: - redirect_uri = self.DEFAULT_REDIRECT_URI - - self.secret_key = app_secret_key + return self._get_auth_url(params, redirect_uri) - url_path = self._get_url_path({ + def get_access_token(self, app_secret_key, secret_code, redirect_uri=DEFAULT_REDIRECT_URI): + params = { "client_id": self.app_id, "client_secret" : app_secret_key, "redirect_uri" : redirect_uri, "code" : secret_code, - }) - url = "%s%s" % (self.BASE_TOKEN_URL, url_path) - - data = self._make_request(url) + } - if not "access_token" in data: - ex = self.factory.make_object('Error', data) - raise PyfbException(ex.error.message) + data = self._make_request(path="oauth/access_token", params=params, auth=False) data = dict(parse_qsl(data)) + self.access_token = data.get('access_token') self.expires = data.get('expires') - return self.access_token - - def get_dialog_url(self, redirect_uri): - if redirect_uri is None: - redirect_uri = self.DEFAULT_DIALOG_URI + return self.access_token - url_path = self._get_url_path({ + def get_dialog_url(self, redirect_uri=DEFAULT_DIALOG_URI): + params = { "app_id" : self.app_id, "redirect_uri": redirect_uri, - }) - url = "%s%s" % (self.DIALOG_BASE_URL, url_path) - return url + } - def get_one(self, path, object_name): + return self._build_url(self.FACEBOOK_DOMAIN, "dialog/feed", params) + + def get_picture_url(self, id, ssl=False, auth=False, size=None): + params = {} + if ssl: + params["return_ssl_resources"] = "1" + if auth: + params["access_token"] = self.access_token + if size is not None: + if isinstance(size, basestring): + params["type"] = size + else: + if isinstance(size, (int, long)): + width = height = size + else: + width, height = map(int, size) + params["width"] = str(width) + params["height"] = str(height) + + return self._build_url(self.GRAPH_DOMAIN, "%s/picture" % id, params) + + def get_one(self, path, object_name, **params): """ Gets one object """ - data = self._make_auth_request(path) - obj = self._make_object(object_name, data) + data = self._make_request(path=path, params=params) - if hasattr(obj, 'error'): - raise PyfbException(obj.error.message) + return self.factory.make_object(object_name, data) - return obj - - def get_list(self, id, path, object_name=None): + def get_list(self, id, path, object_name=None, **params): """ Gets A list of objects """ @@ -169,7 +167,18 @@ def get_list(self, id, path, object_name=None): if object_name is None: object_name = path path = "%s/%s" % (id, path.lower()) - return self.get_one(path, object_name).__dict__[object_name] + + return self.get_one(path, object_name, **params).data + + def post(self, id, path, **data): + """ + Posts data to facebook + """ + if id is None: + id = "me" + path = "%s/%s" % (id, path) + + self._make_request(method="post", path=path, **data) def push(self, id, path, **data): """ @@ -178,14 +187,16 @@ def push(self, id, path, **data): if id is None: id = "me" path = "%s/%s" % (id, path) - self._make_auth_request(path, **data) + + self._make_request(method="put", path=path, **data) def delete(self, id): """ Deletes a object by id """ data = {"method": "delete"} - self._make_auth_request(id, **data) + + self._make_request(method="delete", path=id, **data) def _get_table_name(self, query): """ @@ -204,19 +215,17 @@ def execute_fql_query(self, query): Executes a FBQL query and return a list of objects """ table = self._get_table_name(query) - url_path = self._get_url_path({'query' : query, 'format' : 'json'}) - url = "%s%s" % (self.FBQL_BASE_URL, url_path) - data = self._make_request(url) + params = {'query' : query, 'format' : 'json'} + data = self._make_request(domain=self.API_DOMAIN, path="method/fql.query", params=params) return self.factory.make_objects_list(table, data) - class PyfbException(Exception): - """ - A PyFB Exception class - """ + pass - def __init__(self, value): - self.value = value +class PyfbStatusException(Exception): - def __str__(self): - return repr(self.value) + def __init__(self, type, code, subcode, message): + self.type = type + self.code = code + self.subcode = subcode + super(PyfbStatusException, self).__init__(message) diff --git a/pyfb/pyfb.py b/pyfb/pyfb.py index c1c273d..1ef4f58 100644 --- a/pyfb/pyfb.py +++ b/pyfb/pyfb.py @@ -13,9 +13,9 @@ class Pyfb(object): This class is Facade for FacebookClient """ - def __init__(self, app_id, access_token=None, raw_data=False): + def __init__(self, app_id, access_token=None, timeout=30): - self._client = FacebookClient(app_id, access_token, raw_data) + self._client = FacebookClient(app_id, access_token, timeout=timeout) def authenticate(self): """ @@ -66,6 +66,18 @@ def _show_in_browser(self, url): """ webbrowser.open(url) + def get_picture_url(self, id, ssl=False, auth=False, size=None): + """Returns the url of a profile picture. + + :param ssl: True if the picture needs to be returned over a secure connection. + :param auth: True if authentication is required. + :param size: Can be: + - a predefined string ("square", "small", "normal", "large") + - or a (width, height) tuple + - or a single integer for square picture (width == height) + """ + return self._client.get_picture_url(id, ssl=ssl, auth=auth, size=size) + def set_access_token(self, token): """ Sets the access token. Necessary to make the requests that requires autenthication @@ -80,25 +92,25 @@ def set_permissions(self, permissions): """ self._client.permissions = permissions - def get_myself(self): + def get_myself(self, fields=None): """ Gets myself data """ - return self._client.get_one("me", "FBUser") + return self._client.get_one("me", "FBUser", fields=fields) - def get_user_by_id(self, id=None): + def get_user_by_id(self, id=None, fields=None): """ Gets an user by the id """ if id is None: id = "me" - return self._client.get_one(id, "FBUser") + return self._client.get_one(id, "FBUser", fields=fields) - def get_friends(self, id=None): + def get_friends(self, id=None, fields=None, limit=None, offset=None): """ Gets a list with your friends """ - return self._client.get_list(id, "Friends") + return self._client.get_list(id, "Friends", fields=fields, limit=limit, offset=offset) def get_statuses(self, id=None): """ @@ -118,11 +130,17 @@ def get_comments(self, id=None): """ return self._client.get_list(id, "Comments") - def publish(self, message, id=None): + def publish(self, message, id=None, **kwargs): + """ + Publishes a message on the wall + """ + self._client.post(id, "feed", message=message, **kwargs) + + def action(self, namespace, action_type_name, object_type_name, object_url, id=None, **kwargs): """ Publishes a message on the wall """ - self._client.push(id, "feed", message=message) + self._client.post(id, "%s:%s" % (namespace, action_type_name), **{object_type_name: object_url}) def comment(self, message, id=None): """ diff --git a/pyfb/utils.py b/pyfb/utils.py index 49c1c88..5cd3869 100644 --- a/pyfb/utils.py +++ b/pyfb/utils.py @@ -12,7 +12,7 @@ class NamedObject(object): passed by argument. """ def __new__(cls, name): - return type(str(name), (object, ), {}) + return type(str(name), (object,), {}) class Json2ObjectsFactory(object): @@ -23,35 +23,25 @@ class Json2ObjectsFactory(object): everything into an object. """ - def loads(self, data): - return simplejson.loads(data) - - def make_object(self, name, data): - raw = simplejson.loads(data) - return self._make_object(name, raw) - - def make_objects_list(self, name, data): - raw = simplejson.loads(data) - return self._make_objects_list(name, raw) - def _make_objects_list(self, name, values): - objs = [] - for data in values: - if isinstance(data, dict): - objs.append(self._make_object(name, data)) - else: - objs.append(data) - return objs - - def _make_object(self, name, dic): + return [self._make_object(name, value) for value in values] + + def _make_object_dict(self, name, dic): #Life's easy. For Python Programmers BTW ;-). obj = NamedObject(name) for key, value in dic.iteritems(): - if key == 'data': - key = obj.__name__ - if isinstance(value, list): - value = self._make_objects_list(key, value) - elif isinstance(value, dict): - value = self._make_object(key, value) - setattr(obj, key, value) + setattr(obj, key, self._make_object(name, value)) return obj + + def _make_object(self, name, value): + if isinstance(value, dict): + return self._make_object_dict(name, value) + elif isinstance(value, list): + return self._make_objects_list(name, value) + else: + return value + + def make_object(self, name, value): + value = simplejson.loads(value) + + return self._make_object(name, value) diff --git a/setup.py b/setup.py index 54681d6..b24362a 100644 --- a/setup.py +++ b/setup.py @@ -2,19 +2,20 @@ # -*- coding: utf-8 -*- from setuptools import setup, find_packages -__version__ = "0.3.2" +__version__ = "0.3.9" setup( name='pyfb', version=__version__, description='A Python Interface to the Facebook Graph API', author='Juan Manuel GarcĂ­a', - author_email = "jmg.utn@gmail.com", - license = "GPL v3", - keywords = "Facebook Graph API Wrapper Python", + author_email="jmg.utn@gmail.com", + license="GPL v3", + keywords="Facebook Graph API Wrapper Python", url='http://code.google.com/p/pyfb/', packages=['pyfb'], install_requires=[ 'simplejson', + "requests" ], )