Skip to content

More strict HTTP calls and better error handling #2

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

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
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
197 changes: 103 additions & 94 deletions pyfb/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
Expand All @@ -98,69 +92,73 @@ 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
"""
params = {
"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
"""
Expand All @@ -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):
"""
Expand All @@ -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):
"""
Expand All @@ -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)
38 changes: 28 additions & 10 deletions pyfb/pyfb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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
Expand All @@ -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):
"""
Expand All @@ -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):
"""
Expand Down
Loading