Пример #1
0
 def test_multiple_parses_are_idempotent(self):
     url = u'http://xn-hllo-bpa.com/path%20withspaces?query=es%25capes&foo=bar#frag%28withescapes%29'
     parse1 = URLObject.parse(url)
     self.assertEqual(unicode(url), unicode(parse1))
     parse2 = URLObject.parse(unicode(parse1))
     self.assertEqual(unicode(url), unicode(parse2))
     self.assertEqual(unicode(parse1), unicode(parse2))
Пример #2
0
 def access_token_url(self):
     """The URL to retrieve to exchange a code for an access token."""
     
     url = URLObject.parse('https://graph.facebook.com/oauth/access_token')
     url |= ('code', self.request.GET['code'])
     url |= ('client_id', self.client_id())
     url |= ('client_secret', self.client_secret())
     url |= ('redirect_uri', self.redirect_uri())
     return unicode(url)
Пример #3
0
    def url_with_page_number(self, page_number):
        """ Constructs a url used for getting the next/previous urls """
        url = URLObject.parse(self.request.get_full_path())
        url = url.add_query_param('page', page_number)

        limit = self.get_limit()
        if limit != self.limit:
            url = url.add_query_param('limit', limit)

        return url
Пример #4
0
 def authorize_url(self):
     """The URL to redirect the client to for authorization."""
     
     url = URLObject.parse('https://graph.facebook.com/oauth/authorize')
     url |= ('client_id', self.client_id())
     url |= ('redirect_uri', self.redirect_uri())
     
     scope = self.scope()
     if scope:
         url |= ('scope', ','.join(scope))
     
     display = self.display()
     if display:
         url |= ('display', display)
     
     return url
Пример #5
0
 def __init__(self, url, port):
     self.url = URL.parse(url).with_port(port)
Пример #6
0
 def __init__(self, url, port):
     self.url = URL.parse(url).with_port(port)
Пример #7
0
 def setUp(self):
     self.url = URLObject.parse(u'http://www.google.com/search?q=something&hl=en#frag')
Пример #8
0
 def test_host_idna_encoding_is_parsed(self):
     url = URLObject.parse(u'http://xn--hllo-bpa.com/')
     self.assertEqual(url.host, u'héllo.com')
Пример #9
0
 def test_query_is_not_double_escaped(self):
     url = URLObject.parse('http://www.google.com/search?q=a%20string%20with%20escapes')
     self.assertEqual(unicode(url), 'http://www.google.com/search?q=a%20string%20with%20escapes')
     self.assertEqual(url.query, 'q=a%20string%20with%20escapes')
Пример #10
0
 def url(self):
     return URLObject.parse(self.BASE_URL)
Пример #11
0
 def test_path_is_not_double_escaped(self):
     url = URLObject.parse('http://www.google.com/path%20with%20spaces')
     self.assertEqual(unicode(url), 'http://www.google.com/path%20with%20spaces')
     self.assertEqual(url.path, '/path with spaces')
Пример #12
0
 def test_fragment_is_not_double_escaped(self):
     url = URLObject.parse('http://google.com/#frag%20with%20escapes')
     self.assertEqual(unicode(url), 'http://google.com/#frag%20with%20escapes')
     self.assertEqual(url.fragment, 'frag with escapes')
Пример #13
0
class FQL(object):
    """
    A maker of single and multiple FQL queries.
    
    Usage
    =====
    
    Single queries:
    
        >>> q = FQL('access_token')
        >>> result = q("SELECT post_id FROM stream WHERE source_id = ...")
        >>> result
        [Bunch(post_id='XXXYYYZZZ'), ...]
        
        >>> result[0]
        Bunch(post_id='XXXYYYZZZ')
        
        >>> result[0].post_id
        'XXXYYYZZZ'
    
    Multiple queries:
    
        >>> q = FQL('access_token')
        >>> result = q.multi(dict(query1="SELECT...", query2="SELECT..."))
        
        >>> result[0].name
        'query1'
        >>> result[0].fql_result_set
        [...]
        
        >>> result[1].name
        'query2'
        >>> result[1].fql_result_set
        [...]
    
    """

    ENDPOINT = URLObject.parse('https://api.facebook.com/method/')

    def __init__(self, access_token=None, err_handler=None):
        self.access_token = access_token
        self.err_handler = err_handler

    def __call__(self, query, **params):
        """
        Execute a single FQL query (using `fql.query`).
        
        Example:
        
            >>> q = FQL('access_token')
            >>> result = q("SELECT post_id FROM stream WHERE source_id = ...")
            >>> result
            [Bunch(post_id='XXXYYYZZZ'), ...]
            
            >>> result[0]
            Bunch(post_id='XXXYYYZZZ')
            
            >>> result[0].post_id
            'XXXYYYZZZ'
        
        """

        url = self.ENDPOINT / 'fql.query'
        params.update(query=query,
                      access_token=self.access_token,
                      format='json')
        url |= params

        return self.fetch_json(url)

    def multi(self, queries, **params):
        """
        Execute multiple FQL queries (using `fql.multiquery`).
        
        Example:
        
            >>> q = FQL('access_token')
            >>> result = q.multi(dict(query1="SELECT...", query2="SELECT..."))
            
            >>> result[0].name
            'query1'
            >>> result[0].fql_result_set
            [...]
            
            >>> result[1].name
            'query2'
            >>> result[1].fql_result_set
            [...]
        
        """

        url = self.ENDPOINT / 'fql.multiquery'
        params.update(queries=json.dumps(queries),
                      access_token=self.access_token,
                      format='json')
        url |= params

        return self.fetch_json(url)

    def fetch_json(self, url, data=None):
        response = json.loads(self.__class__.fetch(url, data=data))
        if isinstance(response, dict):
            if response.get("error_msg"):
                code = response.get("error_code")
                msg = response.get("error_msg")
                args = response.get("request_args")
                e = GraphException(code, msg, args=args)
                if self.err_handler:
                    self.err_handler(e)
                else:
                    raise e
        return bunch.bunchify(response)

    @staticmethod
    def fetch(url, data=None):
        conn = urllib2.urlopen(url, data=data)
        try:
            return conn.read()
        finally:
            conn.close()
Пример #14
0
 def test_host_idna_encoding_is_preserved(self):
     url = URLObject.parse(u'http://xn--hllo-bpa.com/')
     self.assertEqual(unicode(url), u'http://xn--hllo-bpa.com/')
Пример #15
0
class Graph(object):
    """
    Proxy for accessing the Facebook Graph API.
    
    This class uses dynamic attribute handling to provide a flexible and
    future-proof interface to the Graph API.
    
    Tutorial
    ========
    
    To get started using the API, create a new `Graph` instance with an access
    token:
    
        >>> g = Graph(access_token)  # Access token is optional.
        >>> g
        <Graph('https://graph.facebook.com/') at 0x...>
    
    Addressing Nodes
    ----------------
    
    Each `Graph` contains an access token and a URL. The graph you just created
    will have its URL set to 'https://graph.facebook.com/' by default (this is
    defined as the class attribute `Graph.API_ROOT`). The URL is represented as
    a `URLObject`; see <http://github.com/zacharyvoase/urlobject> for more
    information. Remember that you can treat it exactly as you would a `unicode`
    string; it just supports a few more methods to enable easy URL manipulation.
    
        >>> g.url
        <URLObject(u'https://graph.facebook.com/') at 0x...>
        >>> print g.url
        https://graph.facebook.com/
    
    To address child nodes within the Graph API, `Graph` supports dynamic
    attribute and item lookups:
    
        >>> g.me
        <Graph('https://graph.facebook.com/me') at 0x...>
        >>> g.me.home
        <Graph('https://graph.facebook.com/me/home') at 0x...>
        >>> g['me']['home']
        <Graph('https://graph.facebook.com/me/home') at 0x...>
        >>> g[123456789]
        <Graph('https://graph.facebook.com/123456789') at 0x...>
    
    Note that a `Graph` instance is rarely modified; these methods return copies
    of the original object. In addition, the API is lazy: HTTP requests will
    never be made unless you explicitly make them.
    
    Retrieving Nodes
    ----------------
    
    You can fetch data by calling a `Graph` instance:
    
        >>> about_me = g.me()
        >>> about_me
        Node({'about': '...', 'id': '1503223370'})
    
    This returns a `Node` object, which contains the retrieved data. `Node` is
    a subclass of `bunch.Bunch`, so you can access keys using attribute syntax:
    
        >>> about_me.id
        '1503223370'
        >>> about_me.first_name
        'Zachary'
        >>> about_me.hometown.name
        'London, United Kingdom'
    
    Accessing non-existent attributes or items will return a `Graph` instance
    corresponding to a child node. This `Graph` can then be called normally, to
    retrieve the child node it represents:
    
        >>> about_me.home
        <Graph('https://graph.facebook.com/me/home') at 0x...>
        >>> about_me.home()
        Node({'data': [...]})
    
    See `Node`’s documentation for further examples.
    
    Creating, Updating and Deleting Nodes
    -------------------------------------
    
    With the Graph API, node manipulation is done via HTTP POST requests. The
    `post()` method on `Graph` instances will POST to the current URL, with
    varying semantics for each endpoint:
    
        >>> post = g.me.feed.post(message="Test.")  # Status update
        >>> post
        Node({'id': '...'})
        >>> g[post.id].comments.post(message="A comment.") # Comment on the post
        Node({'id': '...'})
        >>> g[post.id].likes.post()  # Like the post
        True
        
        >>> event = g[121481007877204]()
        >>> event.name
        'Facebook Developer Garage London May 2010'
        >>> event.rsvp_status is None
        True
        >>> event.attending.post()  # Attend the given event
        True
    
    Deletes are just POST requests with `?method=delete`; the `delete()` method
    is a helpful shortcut:
    
        >>> g[post.id].delete()
        True
    
    """

    API_ROOT = URLObject.parse('https://graph.facebook.com/')
    DEFAULT_TIMEOUT = 0  # No timeout as default

    def __init__(self,
                 access_token=None,
                 err_handler=None,
                 timeout=DEFAULT_TIMEOUT,
                 retries=5,
                 urllib2=None,
                 httplib=None,
                 **state):
        self.access_token = access_token
        self.err_handler = err_handler
        self.url = self.API_ROOT
        self.timeout = timeout
        self.retries = retries
        self.__dict__.update(state)
        if urllib2 is None:
            import urllib2
        self.urllib2 = urllib2
        if httplib is None:
            import httplib
        self.httplib = httplib

    def __repr__(self):
        return '<Graph(%r) at 0x%x>' % (str(self.url), id(self))

    def copy(self, **update):
        """Copy this Graph, optionally overriding some attributes."""

        return type(self)(access_token=self.access_token,
                          err_handler=self.err_handler,
                          timeout=self.timeout,
                          retries=self.retries,
                          urllib2=self.urllib2,
                          httplib=self.httplib,
                          **update)

    def __getitem__(self, item):
        if isinstance(item, slice):
            params = {'offset': item.start, 'limit': item.stop - item.start}
            return self.copy(url=(self.url & params))
        return self.copy(url=(self.url / unicode(item)))

    def __getattr__(self, attr):
        return self[attr]

    def __or__(self, params):
        return self.copy(url=(self.url | params))

    def __and__(self, params):
        return self.copy(url=(self.url & params))

    def __call__(self, **params):
        """Read the current URL, and JSON-decode the results."""

        if self.access_token:
            params['access_token'] = self.access_token
        data = json.loads(
            self.fetch(self.url | params,
                       timeout=self.timeout,
                       retries=self.retries,
                       urllib2=self.urllib2,
                       httplib=self.httplib))
        return self.node(data, params)

    def __iter__(self):
        raise TypeError('%r object is not iterable' % self.__class__.__name__)

    def __sentry__(self):
        return 'Graph(url: %s, params: %s)' % (self.url, repr(self.__dict__))

    def fields(self, *fields):
        """Shortcut for `?fields=x,y,z`."""

        return self | ('fields', ','.join(fields))

    def ids(self, *ids):
        """Shortcut for `?ids=1,2,3`."""

        return self | ('ids', ','.join(map(str, ids)))

    def node(self, data, params, method=None):
        return Node._new(self,
                         data,
                         err_handler=self.err_handler,
                         params=params,
                         method=method)

    def post(self, **params):
        """
        POST to this URL (with parameters); return the JSON-decoded result.
        
        Example:
        
            >>> Graph('ACCESS TOKEN').me.feed.post(message="Test.")
            Node({'id': '...'})
        
        Some methods allow file attachments so uses MIME request to send those through.
        Must pass in a file object as 'file'
        """

        if self.access_token:
            params['access_token'] = self.access_token

        if self.url.path.split('/')[-1] in ['photos']:
            params['timeout'] = self.timeout
            params['httplib'] = self.httplib
            fetch = partial(self.post_mime,
                            self.url,
                            httplib=self.httplib,
                            retries=self.retries,
                            **params)
        else:
            params = dict([(k, v.encode('UTF-8'))
                           for (k, v) in params.iteritems() if v is not None])
            fetch = partial(self.fetch,
                            self.url,
                            urllib2=self.urllib2,
                            httplib=self.httplib,
                            timeout=self.timeout,
                            retries=self.retries,
                            data=urllib.urlencode(params))

        data = json.loads(fetch())
        return self.node(data, params, "post")

    def post_file(self, file, **params):

        if self.access_token:
            params['access_token'] = self.access_token
        params['file'] = file
        params['timeout'] = self.timeout
        params['httplib'] = self.httplib
        data = json.loads(self.post_mime(self.url, **params))

        return self.node(data, params, "post_file")

    @staticmethod
    def post_mime(url,
                  httplib=default_httplib,
                  timeout=DEFAULT_TIMEOUT,
                  retries=5,
                  **kwargs):

        body = []
        crlf = '\r\n'
        boundary = "graphBoundary"

        # UTF8 params
        utf8_kwargs = dict([(k, v.encode('UTF-8'))
                            for (k, v) in kwargs.iteritems()
                            if k != 'file' and v is not None])

        # Add args
        for (k, v) in utf8_kwargs.iteritems():
            body.append("--" + boundary)
            body.append('Content-Disposition: form-data; name="%s"' % k)
            body.append('')
            body.append(str(v))

        # Add raw data
        file = kwargs.get('file')
        if file:
            file.open()
            data = file.read()
            file.close()

            body.append("--" + boundary)
            body.append(
                'Content-Disposition: form-data; filename="facegraphfile.png"')
            body.append('')
            body.append(data)

            body.append("--" + boundary + "--")
            body.append('')

        body = crlf.join(body)

        # Post to server
        kwargs = {}
        if timeout:
            kwargs = {'timeout': timeout}
        r = httplib.HTTPSConnection(url.host, **kwargs)
        headers = {
            'Content-Type': 'multipart/form-data; boundary=%s' % boundary,
            'Content-Length': str(len(body)),
            'MIME-Version': '1.0'
        }

        r.request('POST', url.path.encode(), body, headers)
        attempt = 0
        while True:
            try:
                return r.getresponse().read()
            except (httplib.BadStatusLine, IOError):
                if attempt < retries:
                    attempt += 1
                else:
                    raise
            finally:
                r.close()

    def delete(self):
        """Delete this resource. Sends a POST with `?method=delete`."""

        return self.post(method='delete')

    @staticmethod
    def fetch(url,
              data=None,
              urllib2=default_urllib2,
              httplib=default_httplib,
              timeout=DEFAULT_TIMEOUT,
              retries=None):
        """
        Fetch the specified URL, with optional form data; return a string.
        
        This method exists mainly for dependency injection purposes. By default
        it uses urllib2; you may override it and use an alternative library.
        """
        conn = None
        attempt = 0
        while True:
            try:
                kwargs = {}
                if timeout:
                    kwargs = {'timeout': timeout}
                conn = urllib2.urlopen(url, data=data, **kwargs)
                return conn.read()
            except urllib2.HTTPError, e:
                return e.fp.read()
            except (httplib.BadStatusLine, IOError):
                if attempt < retries:
                    attempt += 1
                else:
                    raise
            finally:
def add_query_param(url, param):
    (key, sep, val) = param.partition('=')
    return unicode(URLObject.parse(url) & (key, val))
Пример #17
0
 def previous_page(self):
     """Shortcut for a `Graph` pointing to the previous page."""
     
     return self._api.copy(url=URLObject.parse(self.paging.next))
Пример #18
0
    def render(self, context):

        kwargs = MultiValueDict()
        for key in self.kwargs:
            key = smart_str(key, 'ascii')
            values = [value.resolve(context) for value in self.kwargs.getlist(key)]
            kwargs.setlist(key, values)

        if 'base' in kwargs:
            url = URLObject.parse(kwargs['base'])
        else:
            url = URLObject(scheme='http')

        if 'secure' in kwargs:
            if convert_to_boolean(kwargs['secure']):
                url = url.with_scheme('https')
            else:
                url = url.with_scheme('http')

        if 'query' in kwargs:
            query = kwargs['query']
            if isinstance(query, basestring):
                query = render_template_from_string_without_autoescape(query, context)
            url = url.with_query(query)

        if 'add_query' in kwargs:
            for query_to_add in kwargs.getlist('add_query'):
                if isinstance(query_to_add, basestring):
                    query_to_add = render_template_from_string_without_autoescape(query_to_add, context)
                    query_to_add = dict(decode_query(query_to_add))
                for key, value in query_to_add.items():
                    url = url.add_query_param(key, value)

        if 'scheme' in kwargs:
            url = url.with_scheme(kwargs['scheme'])

        if 'host' in kwargs:
            url = url.with_host(kwargs['host'])

        if 'path' in kwargs:
            url = url.with_path(kwargs['path'])

        if 'add_path' in kwargs:
            for path_to_add in kwargs.getlist('add_path'):
                url = url.add_path_component(path_to_add)

        if 'fragment' in kwargs:
            url = url.with_fragment(kwargs['fragment'])

        if 'port' in kwargs:
            url = url.with_port(kwargs['port'])

        # sensible default
        if not url.host:
            url = url.with_scheme('')

        # Convert the URLObject to its unicode representation
        url = unicode(url)

        # Handle escaping. By default, use the value of
        # context.autoescape. This can be overridden by
        # passing an "autoescape" keyword to the tag.
        if 'autoescape' in kwargs:
            autoescape = convert_to_boolean(kwargs['autoescape'])
        else:
            autoescape = context.autoescape

        if autoescape:
            url = escape(url)

        if self.asvar:
            context[self.asvar] = url
            return ''

        return url
Пример #19
0
    def previous_page(self):
        """Shortcut for a `Graph` pointing to the previous page."""

        return self._api.copy(url=URLObject.parse(self.paging.next))