Skip to content

Commit

Permalink
Initial, v0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Greenberg committed Jul 4, 2013
0 parents commit b4b5f5d
Show file tree
Hide file tree
Showing 8 changed files with 575 additions and 0 deletions.
20 changes: 20 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (c) 2013 Leandigo (www.leandigo.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include LICENSE
include README.rst
220 changes: 220 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
pyoneall - OneAll API Wrapper
=============================

**OneAll** (|oneall|_) provides web-applications with a unified API for **20+ social networks**.
**pyoneall** provides developers with OneAll accounts a simple interface with the OneAll API for Python-based web-applications.

Disclaimer
----------
*This package is new, and so far has been tested in a development of a small number of projects.*
*Please be sure to test all edge-cases where this package is used with your application!*

Implementation Overview
-----------------------
OneAll API documentation is available at |onealldoc|_. However, in order to use pyoneall with your application, it's
enough to read the docs for the Connection API: `Connection API Documentation`_.

So far, we have tested pyoneall within Flask and Django apps. To use OneAll as a Django authentication backend,
please check out our ``django_oneall`` project, which relies on this package.

pyoneall defines the ``OneAll`` class, which is the API client. As of now, it has the following methods:

:``connections()``: Get a list of social connections to the site
:``connection()``: Get detailed connection data including user details
:``users()``: Get a list of users that have connected with the site
:``user()``: Get detailed user data
:``user_contacts()``: Get a list of user's contacts
:``publish()``: Publish a message using user's social network account

As pyoneall wraps a REST API which returns JSON objects, the objects returned by the methods behave in a somewhat
JavaScript-like manner. This means that in addition to the ``dict``-style ``object['key']`` notation, you can also
use ``object.key``.

Also, arrays nested in the JSON responses, are represented by a class that defines a ``by_*()`` grouping and searching
method in an addition to the ``list`` methods it inherits from.

For more information on these classes, check out ``help(pyoneall.base.OADict)`` and ``help(pyoneall.base.OAList)``.

Example
-------

Authentication
~~~~~~~~~~~~~~
Access to the OneAll API requires authentication. Obtain your API credentials following the procedure described at
`Authentication Documentation`_.

Create an instance of the OneAll client::

from pyoneall import OneAll

oa = OneAll(
site_name='NAME OF YOUR ONEALL SITE',
public_key='PUBLIC KEY OF YOUR SITE',
private_key='PRIVATE KEY OF YOUR SITE'
)

The Connection API
~~~~~~~~~~~~~~~~~~
Fetching connections lists
**************************
::

connections = oa.connections()

``connections`` now contains the "connections" portion of the result of the API call, as described in
`<http://docs.oneall.com/api/resources/connections/list-all-connections/>`_.
Full response data (for debugging and whatnot) is in ``connections.response``.

OneAll uses pagination for calls which contain many entries. Each call returns a page up to 500 entries. When the
``OneAll.connections()`` method is executed without arguments, only the first page is loaded. You the access the
pagination information in ``connections.pagination``.

In order to load a custom range of pages, you can do something like::

connections = oa.connections(first_page=3, last_page=6)

Or, if you want to load all pages, use::

connections = oa.connections(fetch_all=True)

Of course, this will result in multiple API calls.

The connections list itself is in ``connections.entries``::

>>> connections.entries
[{u'callback_uri': u'http://www.example.com/connect/',
u'connection_token': u'cf2fffc7-34dc-484e-95cd-13f8ab838e22',
u'date_creation': u'Sun, 23 Jun 2013 14:12:43 +0200',
u'status': u'succeeded'},
{u'callback_uri': u'http://m.example.com/connect/',
u'connection_token': u'4276bd23-3605-4679-acd2-963148c477cc',
u'date_creation': u'Sun, 23 Jun 2013 14:13:20 +0200',
u'status': u'succeeded'},
{u'callback_uri': u'http://www.example.com/connect/',
u'connection_token': u'58ad2a04-ed1e-4799-a3ca-2b26651e35a0',
u'date_creation': u'Sun, 23 Jun 2013 14:18:00 +0200',
u'status': u'succeeded'},
{u'callback_uri': u'http://m.example.com/connect/',
u'connection_token': u'e5231790-c6dc-4ce8-9922-792a2aebbba2',
u'date_creation': u'Sun, 23 Jun 2013 14:18:11 +0200',
u'status': u'succeeded'},
{u'callback_uri': u'http://www.example.com/connect/',
u'connection_token': u'f82ad1e5-113f-46a2-b1c5-2a57a6002401',
u'date_creation': u'Sun, 23 Jun 2013 14:21:14 +0200',
u'status': u'succeeded'}]

In the example above, you can see that some connections were made with the callback of the desktop website
(``http://www.example.com/connect/``), and some were made with the mobile webapp (``http://m.example.com/connect/``).
We can get an object grouped by the "callback_uri" using::

>>> connections.entries.by_callback_uri()
{u'http://www.example.com/connect/': [
{u'callback_uri': u'http://www.example.com/connect/',
u'connection_token': u'cf2fffc7-34dc-484e-95cd-13f8ab838e22',
u'date_creation': u'Sun, 23 Jun 2013 14:12:43 +0200',
u'status': u'succeeded'},
{u'callback_uri': u'http://www.example.com/connect/',
u'connection_token': u'58ad2a04-ed1e-4799-a3ca-2b26651e35a0',
u'date_creation': u'Sun, 23 Jun 2013 14:18:00 +0200',
u'status': u'succeeded'}],
{u'callback_uri': u'http://www.example.com/connect/',
u'connection_token': u'f82ad1e5-113f-46a2-b1c5-2a57a6002401',
u'date_creation': u'Sun, 23 Jun 2013 14:21:14 +0200',
u'status': u'succeeded'},
u'http://m.example.com/connect/': [
{u'callback_uri': u'http://m.example.com/connect/',
u'connection_token': u'4276bd23-3605-4679-acd2-963148c477cc',
u'date_creation': u'Sun, 23 Jun 2013 14:13:20 +0200',
u'status': u'succeeded'},
{u'callback_uri': u'http://m.example.com/connect/',
u'connection_token': u'e5231790-c6dc-4ce8-9922-792a2aebbba2',
u'date_creation': u'Sun, 23 Jun 2013 14:18:11 +0200',
u'status': u'succeeded'}]}

Or get a list of connections with a specific "callback_uri"::

>>> connections.entries.by_callback_uri('http://m.example.com/connect/')
[{u'callback_uri': u'http://m.example.com/connect/',
u'connection_token': u'4276bd23-3605-4679-acd2-963148c477cc',
u'date_creation': u'Sun, 23 Jun 2013 14:13:20 +0200',
u'status': u'succeeded'},
{u'callback_uri': u'http://m.example.com/connect/',
u'connection_token': u'e5231790-c6dc-4ce8-9922-792a2aebbba2',
u'date_creation': u'Sun, 23 Jun 2013 14:18:11 +0200',
u'status': u'succeeded'}]

Reading connection details
**************************
In order to get the **user_token** and the user's social identity you can pass a **connection_token** to the
``connection()`` method of the ``OneAll`` instance::

some_connection = oa.connection('e5231790-c6dc-4ce8-9922-792a2aebbba2')

Or, alternatively you can fetch the connection details through the ``connection()`` method of an entry in the list
of connections::

some_connection = connections.entries[3].connection()

``some_connection`` will now contain the "connection" portion of the response described in the API documentation for
`Read Connection Details`_, most importantly ``some_connection.user`` and ``some_connection.user.user_token``

The User API
~~~~~~~~~~~~
Fetching user list
******************
``OneAll.users()`` behaves the same way ``OneAll.connections()`` does, arguments and all. This is due to the similarity
of the List Users and the List Connections API, in terms of pagination and entries structure.
::

users = oa.users()

Now, you can access ``users.entries``, or even access detailed user data with ``users.entries[4].user()``.

Reading user details
********************
Read user details using::

user_token = some_connection.user.user_token
some_user = oa.user(user_token)

``some_user`` will contain the "user" portion of the response detailed at
`<http://docs.oneall.com/api/resources/users/read-user-details/>`_.

Reading user's contacts
***********************
You can get the user's contacts (depending on the social network) with::

contacts = some_user.contacts()

or, with::

contacts = oa.user_contacts(user_token)

Publishing content on user's behalf
***********************************
First, you need to format a message as described at `<http://docs.oneall.com/api/resources/users/write-to-users-wall/>`_.
Afterwards, publish it using ``publish()``::

message = {
'request': {
'message': {
'parts': {
'text': {
'body': 'Hello World!' }}}}}

oa.publish(user_token, message)

License
-------
Copyright (c) 2013, Leandigo (|leandigo|_)
Released under the MIT License. See the LICENSE file for details.

.. |leandigo| replace:: www.leandigo.com
.. _leandigo: http://www.leandigo.com
.. |oneall| replace:: http://www.oneall.com
.. _oneall: http://www.oneall.com
.. |onealldoc| replace:: http://docs.oneall.com
.. _onealldoc: http://docs.oneall.com
.. _Connection API Documentation: http://docs.oneall.com/api/resources/connections/
.. _Authentication Documentation: http://docs.oneall.com/api/basic/authentication/
.. _Read Connection Details: http://docs.oneall.com/api/resources/connections/read-connection-details/
1 change: 1 addition & 0 deletions pyoneall/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from connection import OneAll
82 changes: 82 additions & 0 deletions pyoneall/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
class OAList(list):
"""
A List of representing a JSON array with nested objects.
"""
each = None

def __init__(self, init_list=None):
"""
A constructor which receives an iterable, and recursively converts all nested list and dict objects to
OAList and OADict respectively. It also creates by_* methods allowing to perform grouping and search
on OADict objects within the list.
If subclass has the `each` attribute set to a class, all objects in list will be initialized as instances
of that class.
:param iterable init_list: Values to initiate list with.
"""
if self.each:
super(OAList, self).__init__([self.each(**item) for item in init_list])
else:
super(OAList, self).__init__([_convert_base(item) for item in init_list])

self._dict_items = [item for item in self if isinstance(item, OADict)]

for key in set().union(*[item.keys() for item in self._dict_items]):
setattr(self, 'by_%s' % key, self._by_attr(key))

def _by_attr(self, attr):
"""
Create a custom by_attr function for grouping and searching
"""
def by_attr(query=None, first=False):
if query:
ret = []
for item in self._dict_items:
if item.get(attr) == query:
if first:
return item
ret.append(item)
return ret

else:
ret = {}
[ret.setdefault(item.get(attr), []).append(item) for item in self._dict_items
if not (isinstance(item.get(attr), OADict) or isinstance(item.get(attr), OAList))]
return ret
return by_attr

def drop_nulls(self):
while self.count(None):
self.remove(None)
[item.drop_nulls() for item in self if isinstance(item, OADict) or isinstance(item, OAList)]

class OADict(dict):
"""
A JavaScript-object-like representing a JSON object with nested objects
"""
ignored = ['response', 'oneall']

def __init__(self, *args, **kwargs):
super(OADict, self).__init__(
*args,
**dict((key, _convert_base(value)) for key, value in kwargs.iteritems())
)
[setattr(self, key, value) for key, value in self.iteritems()]
self._populate()

def __setattr__(self, key, value):
super(OADict, self).__setattr__(key, value)
if key not in self.ignored: super(OADict, self).__setitem__(key, value)

def __setitem__(self, key, value):
super(OADict, self).__setitem__(key, value)
super(OADict, self).__setattr__(key, value)

def _populate(self):
pass

def _convert_base(item):
return {
dict : lambda: OADict(**item),
list : lambda: OAList(item),
}.get(type(item), lambda: item)()
Loading

0 comments on commit b4b5f5d

Please sign in to comment.