--- /dev/null
+## Changelog for `diaspy`, unofficla Diaspora\* API for Python
+
+This changelog file follows few rules:
+
+* __rem__: indicates removed features,
+* __new__: indicates new features,
+* __upd__: indicates updated features,
+* __dep__: indicates deprecated features,
+
+Deprecation means that in the next version feature will be removed.
+
+Also, after every version there should be a brief note describing possible
+problems with migrating to it from older versions and usage of new features.
+
+Users can always read the manual and dcumentation to make themselves more knowledgeable and
+are encouraged to do so. They only need to remember that documentation is usually more
+up-to-date than manual and if conflicts appear they should follow the order:
+
+*docstrings* -> *docs/* -> *manual/*
+
+
+----
+
+Version `0.3.0` (2013-07-07):
+
import diaspy.notifications as notifications
-__version__ = '0.2.0'
+__version__ = '0.3.0'
"""
request = self.get('stream')
token = self._token_regex.search(request.text).group(1)
+ self.token = token
return token
- def get_token(self, new=False):
+ def get_token(self, fetch=False):
"""This function returns a token needed for authentication in most cases.
Each time it is run a _fetchtoken() is called and refreshed token is stored.
:returns: string -- token used to authenticate
"""
try:
- if new: self.token = self._fetchtoken()
- if not self.token: self.token = self._fetchtoken()
+ if fetch: self._fetchtoken()
+ if not self.token: self._fetchtoken()
except requests.exceptions.ConnectionError as e:
warnings.warn('{0} was cought: reusing old token'.format(e))
finally:
from diaspy import search
+def sephandle(handle):
+ """Separate Diaspora* handle into pod pod and user.
+
+ :returns: two-tuple (pod, user)
+ """
+ if re.match('^[a-zA-Z]+[a-zA-Z0-9_-]*@[a-z0-9.]+\.[a-z]+$', handle) is None:
+ raise errors.UserError('invalid handle: {0}'.format(handle))
+ handle = handle.split('@')
+ pod, user = handle[1], handle[0]
+ return (pod, user)
+
+
class User():
"""This class abstracts a D* user.
This object goes around the limitations of current D* API and will
elif fetch == 'data' and self.handle:
self.fetchprofile()
- def _sephandle(self):
- """Separate D* handle into pod pod and user.
-
- :returns: two-tuple (pod, user)
- """
- if re.match('^[a-zA-Z]+[a-zA-Z0-9_-]*@[a-z0-9.]+\.[a-z]+$', self.handle) is None:
- raise errors.UserError('invalid handle: {0}'.format(self.handle))
- handle = self.handle.split('@')
- pod, user = handle[1], handle[0]
- return (pod, user)
-
def _finalize_data(self, data):
- names = [('id', 'id'),
- ('handle', 'diaspora_id'),
- ('guid', 'guid'),
- ('name', 'diaspora_name'),
- ('avatar', 'image_urls'),
- ]
+ """Adjustments are needed to have similar results returned
+ by search feature and fetchguid/handle().
+ """
+ names = [ ('id', 'id'),
+ ('guid', 'guid'),
+ ('name', 'name'),
+ ('avatar', 'avatar'),
+ ('handle', 'diaspora_id'),
+ ]
final = {}
- for d, f in names:
+ for f, d in names:
final[f] = data[d]
return final
:param request: request object
:type request: request
"""
- if request.status_code != 200:
- raise Exception('wrong error code: {0}'.format(request.status_code))
- else:
- request = request.json()
+ if request.status_code != 200: raise Exception('wrong error code: {0}'.format(request.status_code))
+ else: request = request.json()
if not len(request): raise errors.UserError('cannot extract user data: no posts to analyze')
self.data = self._finalize_data(request[0]['author'])
self.stream = Outer(self._connection, location='people/{0}.json'.format(self['guid']))
def fetchhandle(self, protocol='https'):
"""Fetch user data and posts using Diaspora handle.
"""
- pod, user = self._sephandle()
+ pod, user = sephandle(self.handle)
request = self._connection.session.get('{0}://{1}/u/{2}.json'.format(protocol, pod, user))
self._postproc(request)
def fetchprofile(self):
"""Fetches user data.
"""
- self.data = self._finalize_data(search.Search(self._connection).user(self.handle)[0])
+ data = search.Search(self._connection).user(self.handle)[0]
+ self.data = data
class Contacts():
# built documents.
#
# The short X.Y version.
-version = '0.2.0'
+version = '0.3.0'
# The full version, including alpha/beta/rc tags.
-release = '0.2.0'
+release = '0.3.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
--- /dev/null
+search Module
+==============
+
+.. automodule:: diaspy.search
+ :members:
+ :undoc-members:
+ :show-inheritance:
+++ /dev/null
-#### Connecting to D\* pod via `diaspy`
-----
-
-First thing you have to do is to create new instace of `Client()`.
-Then, if no errors are raised, you can `_login()` to the pod with given username and password.
-
- import diaspy
-
- client = diaspy.Client(pod="http://pod.example.com", username="foo", password="bar")
-
-
-If everything worked you are now connected to D\* pod at given URL.
-
-----
-
-###### Manual for `diaspy`, written by Marek Marecki
* `Post()` and `Conversation()` objects require it to authenticate and
do their work,
-* `Client()` uses it for loggin in to pod and other stuff,
+* `Client()` uses it for loging in to pod and other stuff,
+* streams use it for getting their contents,
+* etc.
`Connection()` is the most low-level part of `diaspy` and provides everything
##### Login procedure
-`Client()` object available in `diapsy` will login you automatically - provided
+`Client()` object available in `diaspy` will login you automatically - provided
you gave it valid pod, username and password.
On the other hand, `Connection()` is more stupid and it will not log you in unless
overwritten but if it fails everything else also fails and the current
connection is broken.
+----
+
+##### Authentication
+
+Authentication in Diaspora\* is done with *token*. It is a string
+which is passed to pod with several requests to prove you are who you are
+pretending to be.
+
+`Connection` providesyou with a method to fetch this token and perform
+various actions on your account.
+This method is called `_fetchtoken()`.
+It will try to download new token for you.
+
+Once a token is fetched there is no use for doing it again.
+You can save time by using `get_token()` method.
+It will check whether the token had been already fetched and reuse it.
+This is especially useful on slow or unstable connections.
+`get_token()` has an optional `fetch` argument (it is of `bool` type, by default `False`)
+which will tell it to fetch new token if you find suitable.
+
+However, recommended way of dealing with token is to use `repr()` function on
+`Connection` object. This will allow of your programs to be future-proof because if
+for any reason we will change the way in which authorization is handled `get_token()`
+method may be gone -- `repr()` will stay.
+
+Here is how you should create your auth flow:
+
+ connection = diaspy.connection.Connection(...)
+ connection.login()
+
+ token = repr(connection)
+
----
+##### Note for developers
+
If you want to write your own interface or client for D\*
`Connection()` will be the only object you need.
--- /dev/null
+#### `diaspy` models
+
+Design of models in `diaspy` follow few simple rules.
+
+
+##### Initialization
+
+First argument is always `Connection` object stored in `self._connection`.
+Parameters specific to each model go after it.
+
+If model requires some king of id (guid, conversation id, post id) it is simply `id`.
In order to get list of notifications use `diaspy.notifications.Notifications()` object.
It support iteration and indexing.
+When creating new instance of `Notifications` only `Connection` object is needed.
+
+#### Methods
+
+##### `last()`
+
+This method will return you last five notifications.
+
+##### `get()`
+
+This is slightly more advanced then `last()`. It allows you to specify how many
+notifications per page you want to get and which page you want to recieve.
+
----
#### `Notification()` model
----
-##### Getting user data
+##### `User()` object -- getting user data
-You have to know either GUID or *diaspora_id* of a user.
-Assume that *12345678abcdefgh* and *otheruser@pod.example.com* point to
+You have to know either GUID or *handle* of a user.
+Assume that *1234567890abcdef* and *otheruser@pod.example.com* point to
the same user.
-
>>> c = diaspy.connection.Connection('https://pod.example.com', 'foo', 'bar')
>>>
- >>> user_guid = diaspy.people.User(c)
- >>> user_guid.fetchguid('12345678abcdefgh')
- >>>
- >>> user_handle = diaspy.people.User(c)
- >>> user_handle.fetchhandle('otheruser@pod.example.com')
+ >>> user_guid = diaspy.people.User(c, guid='1234567890abcdef')
+ >>> user_handle = diaspy.people.User(c, handle='otheruser@pod.example.com')
-Now, you have two `User()` objects containing the data of one user.
+Now, you have two `User` objects containing the data of one user.
The object is subscriptable so you can do like this:
- >>> user_guid['diaspora_id']
+ >>> user_guid['handle']
'otheruser@pod.example.com'
>>>
>>> user_handle['guid']
- '12345678abcdefgh'
-
+ '1234567890abcdef'
-User object contains following items in its `data` dict:
+`User` object contains following items in its `data` dict:
* `id`, `str`, id of the user;
* `guid`, `str`, guid of the user;
-* `diaspora_id`, `str`, D\* id of the user;
-* `diaspora_name`, `str`, name of the user;
-* `image_urls`, `dict`, links to avatar of the user;
+* `handle`, `str`, D\* id (or handle) of the user;
+* `name`, `str`, name of the user;
+* `avatar`, `dict`, links to avatars of the user;
-Also `User()` object contains a stream for this user.
+> **Historical note:** the above values were changed in version `0.3.0`.
+> `diaspora_id` became `handle` and `image_urls` became `avatar` to have more
+> consistent results.
+> This is because we can get only user data and this returns dict containing
+> `handle` and `avatar` and not `diaspora_id` and `image_urls`.
+> Users who migrated from version `0.2.x` and before to version `0.3.0` had to
+> update their software.
-* `stream`, `diaspy.streams.Outer`, stream of the user (provides all methods of generic stream);
+Also `User` object contains a stream for this user.
+* `stream`, `diaspy.streams.Outer`, stream of the user (provides all methods of generic stream);
====
The only method of this object is `get()` and its only parameter is `set` which
is optional (defaults to empty string).
-If called without specifying `set` `get()` will return list of users (`User()` objects)
+If called without specifying `set` `get()` will return list of users (`User` objects)
who are in your aspects.
Optional `set` parameter can be either `all` or `only_sharing`.
#### `Post()` object and posting
-`Post()` object is used to represent a post on D\*.
+`Post` object is used to represent a post on D\*.
----
##### Posting
-Posting is done through a `Stream()` object method `post()`.
+Posting is done through a `Stream` object method `post()`.
It supports posting just text, images or text and images.
-`Stream().post()` returns `Post()` object referring to the post
+`Stream().post()` returns `Post` object referring to the post
which have just been created.
stream.post(text='Your post.')
-It will return `Post()` you have just created.
+It will return `Post` you have just created.
##### Posting images
--- /dev/null
+#### `Search()` object
+
+Searching is useful only if it comes to searching for users.
+Tags can be searched for just by creating `Tag` object using
+tag-name as an arument.
+
+----
+
+##### `lookup_user()`
+
+This method is used for telling a pod "Hey, I want this user searchable via you!"
+
+##### `user()`
+
+This method will return you a list of dictionaries containg data of users whose
+handle conatins query you have used.
----
-##### `fill()` and `update()`
+##### `fill()`, `update()` and `more()`
When you want to refresh stream call it's `fill()` method. It will overwrite old stream
contents.
On the contrary, `update()` will get a new stream but will not overwrite old stream saved
in the object memory. It will append every new post to the old stream.
+`more()` complements `update()` it will fetch you older posts instead of newer ones.
+
----
##### Length of and iterating over a stream
This is described in [`posting`](./posting.mdown) document in this manual.
+
+----
+
+##### Clearing stream
+
+##### `clear()`
+
+This will remove all posts from visible stream.
+
+##### `purge()`
+
+This will scan stream for nonexistent posts (eg. deleted) and remove them.
+
----
###### Manual for `diaspy`, written by Marek Marecki
from setuptools import setup, find_packages
setup(name='diaspy',
- version='0.2.0',
+ version='0.3.0',
author='Moritz Kiefer',
author_email='moritz.kiefer@gmail.com',
url='https://github.com/Javafant/diaspora-api',
- description='A python api to the social network diaspora',
+ description='A Python API to the social network Diaspora*',
packages=find_packages(),
install_requires=['requests']
)
test_count_file.close()
print('Running test no. {0}'.format(test_count))
-print('Running tests on connection to pod: "{0}"\t'.format(__pod__), end='')
+print('Running tests on connection: "{0}:{1}@{2}"\t'.format(testconf.__username__, '*'*len(testconf.__passwd__), __pod__), end='')
test_connection = diaspy.connection.Connection(pod=__pod__, username=__username__, password=__passwd__)
test_connection.login()
print('[ CONNECTED ]\n')
class UserTests(unittest.TestCase):
def testHandleSeparatorRaisingExceptions(self):
- user = diaspy.people.User(test_connection)
handles = ['user.pod.example.com',
'user@podexamplecom',
'@pod.example.com',
'user0@pod300 example.com',
]
for h in handles:
- self.assertRaises(Exception, user._sephandle, h)
+ self.assertRaises(Exception, diaspy.people.sephandle, h)
- def testGettingUserByHandle(self):
+ def testGettingUserByHandleData(self):
+ user = diaspy.people.User(test_connection, handle=testconf.diaspora_id, fetch='data')
+ self.assertEqual(testconf.guid, user['guid'])
+ self.assertEqual(testconf.diaspora_id, user['handle'])
+ self.assertEqual(testconf.diaspora_name, user['name'])
+ self.assertEqual(type(user.stream), list)
+ self.assertEqual(user.stream, [])
+ self.assertIn('id', user.data)
+ self.assertIn('avatar', user.data)
+
+ def testGettingUserByHandlePosts(self):
user = diaspy.people.User(test_connection, handle=testconf.diaspora_id)
- user.fetchhandle()
self.assertEqual(testconf.guid, user['guid'])
- self.assertEqual(testconf.diaspora_name, user['diaspora_name'])
+ self.assertEqual(testconf.diaspora_id, user['handle'])
+ self.assertEqual(testconf.diaspora_name, user['name'])
self.assertIn('id', user.data)
- self.assertIn('image_urls', user.data)
+ self.assertIn('avatar', user.data)
self.assertEqual(type(user.stream), diaspy.streams.Outer)
-
+
def testGettingUserByGUID(self):
user = diaspy.people.User(test_connection, guid=testconf.guid)
- user.fetchguid()
- self.assertEqual(testconf.diaspora_id, user['diaspora_id'])
- self.assertEqual(testconf.diaspora_name, user['diaspora_name'])
+ self.assertEqual(testconf.diaspora_id, user['handle'])
+ self.assertEqual(testconf.diaspora_name, user['name'])
self.assertIn('id', user.data)
- self.assertIn('image_urls', user.data)
+ self.assertIn('avatar', user.data)
self.assertEqual(type(user.stream), diaspy.streams.Outer)
else:
warnings.warn('test not sufficient: no unread notifications were found')
-if __name__ == '__main__': unittest.main()
+if __name__ == '__main__':
+ print('Hello World!')
+ print('It\'s testing time!')
+ n = unittest.main()
+ print(n)
+ print('It was testing time!')