From 6d8d47ce5d45b5cec7b9559a17da71250dbc1685 Mon Sep 17 00:00:00 2001 From: Marek Marecki Date: Mon, 8 Jul 2013 01:44:46 +0200 Subject: [PATCH] Update to version 0.3.0 --- Changelog.markdown | 25 +++++++++ diaspy/__init__.py | 2 +- diaspy/connection.py | 7 +-- diaspy/people.py | 51 ++++++++++--------- docs/source/conf.py | 4 +- docs/source/search.rst | 7 +++ manual/connecting_to_pod.mdown | 16 ------ .../{connection.mdown => connection.markdown} | 40 ++++++++++++++- manual/contacts.mdown | 0 manual/models.markdown | 11 ++++ ...fications.mdown => notifications.markdown} | 13 +++++ manual/{people.mdown => people.markdown} | 42 +++++++-------- manual/{posting.mdown => posting.markdown} | 8 +-- manual/search.markdown | 16 ++++++ manual/{stream.mdown => streams.markdown} | 17 ++++++- setup.py | 4 +- tests.py | 39 +++++++++----- 17 files changed, 214 insertions(+), 88 deletions(-) create mode 100644 Changelog.markdown create mode 100644 docs/source/search.rst delete mode 100644 manual/connecting_to_pod.mdown rename manual/{connection.mdown => connection.markdown} (64%) delete mode 100644 manual/contacts.mdown create mode 100644 manual/models.markdown rename manual/{notifications.mdown => notifications.markdown} (70%) rename manual/{people.mdown => people.markdown} (55%) rename manual/{posting.mdown => posting.markdown} (85%) create mode 100644 manual/search.markdown rename manual/{stream.mdown => streams.markdown} (82%) diff --git a/Changelog.markdown b/Changelog.markdown new file mode 100644 index 0000000..abce777 --- /dev/null +++ b/Changelog.markdown @@ -0,0 +1,25 @@ +## 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): + diff --git a/diaspy/__init__.py b/diaspy/__init__.py index 0d3caa3..d62eb86 100644 --- a/diaspy/__init__.py +++ b/diaspy/__init__.py @@ -6,4 +6,4 @@ import diaspy.people as people import diaspy.notifications as notifications -__version__ = '0.2.0' +__version__ = '0.3.0' diff --git a/diaspy/connection.py b/diaspy/connection.py index 4f70c2f..90a2e8b 100644 --- a/diaspy/connection.py +++ b/diaspy/connection.py @@ -164,9 +164,10 @@ class Connection(): """ 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. @@ -177,8 +178,8 @@ class Connection(): :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: diff --git a/diaspy/people.py b/diaspy/people.py index 27dde8c..9eaaccd 100644 --- a/diaspy/people.py +++ b/diaspy/people.py @@ -5,6 +5,18 @@ from diaspy import errors 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 @@ -55,26 +67,18 @@ class User(): 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 @@ -85,10 +89,8 @@ class User(): :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'])) @@ -96,7 +98,7 @@ class User(): 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) @@ -109,7 +111,8 @@ class User(): 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(): diff --git a/docs/source/conf.py b/docs/source/conf.py index b255156..f7db2b4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -51,9 +51,9 @@ copyright = '2013, Moritz Kiefer' # 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. diff --git a/docs/source/search.rst b/docs/source/search.rst new file mode 100644 index 0000000..bed54b8 --- /dev/null +++ b/docs/source/search.rst @@ -0,0 +1,7 @@ +search Module +============== + +.. automodule:: diaspy.search + :members: + :undoc-members: + :show-inheritance: diff --git a/manual/connecting_to_pod.mdown b/manual/connecting_to_pod.mdown deleted file mode 100644 index 5bfe50a..0000000 --- a/manual/connecting_to_pod.mdown +++ /dev/null @@ -1,16 +0,0 @@ -#### 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 diff --git a/manual/connection.mdown b/manual/connection.markdown similarity index 64% rename from manual/connection.mdown rename to manual/connection.markdown index 723ba82..9d4e8af 100644 --- a/manual/connection.mdown +++ b/manual/connection.markdown @@ -5,7 +5,9 @@ It is pushed around and used by various methods and other objects: * `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 @@ -19,7 +21,7 @@ other modules to aid you and you are strongly encouraged to use them. ##### 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 @@ -74,9 +76,43 @@ If login to a new pod is successful your connection is just changed 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. diff --git a/manual/contacts.mdown b/manual/contacts.mdown deleted file mode 100644 index e69de29..0000000 diff --git a/manual/models.markdown b/manual/models.markdown new file mode 100644 index 0000000..910ca26 --- /dev/null +++ b/manual/models.markdown @@ -0,0 +1,11 @@ +#### `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`. diff --git a/manual/notifications.mdown b/manual/notifications.markdown similarity index 70% rename from manual/notifications.mdown rename to manual/notifications.markdown index 057f7ae..4312865 100644 --- a/manual/notifications.mdown +++ b/manual/notifications.markdown @@ -3,6 +3,19 @@ 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 diff --git a/manual/people.mdown b/manual/people.markdown similarity index 55% rename from manual/people.mdown rename to manual/people.markdown index e4563d6..88f18e0 100644 --- a/manual/people.mdown +++ b/manual/people.markdown @@ -4,44 +4,46 @@ This object is used to represent a D\* user. ---- -##### 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); ==== @@ -53,7 +55,7 @@ It may be slightly confusing to use and reading just docs could be not enough. 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`. diff --git a/manual/posting.mdown b/manual/posting.markdown similarity index 85% rename from manual/posting.mdown rename to manual/posting.markdown index 5dff0a3..ca76707 100644 --- a/manual/posting.mdown +++ b/manual/posting.markdown @@ -1,15 +1,15 @@ #### `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. @@ -20,7 +20,7 @@ If you want to post just text you should call `post()` method with stream.post(text='Your post.') -It will return `Post()` you have just created. +It will return `Post` you have just created. ##### Posting images diff --git a/manual/search.markdown b/manual/search.markdown new file mode 100644 index 0000000..69bfa9e --- /dev/null +++ b/manual/search.markdown @@ -0,0 +1,16 @@ +#### `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. diff --git a/manual/stream.mdown b/manual/streams.markdown similarity index 82% rename from manual/stream.mdown rename to manual/streams.markdown index 7f21f4f..4f2cb26 100644 --- a/manual/stream.mdown +++ b/manual/streams.markdown @@ -21,7 +21,7 @@ Now you have a stream filled with posts (if any can be found on user's stream). ---- -##### `fill()` and `update()` +##### `fill()`, `update()` and `more()` When you want to refresh stream call it's `fill()` method. It will overwrite old stream contents. @@ -29,6 +29,8 @@ 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 @@ -58,6 +60,19 @@ Second, iterating directly over the stream contents: 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 diff --git a/setup.py b/setup.py index 8604d13..e09a709 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,10 @@ 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'] ) diff --git a/tests.py b/tests.py index 1302b63..2949be9 100644 --- a/tests.py +++ b/tests.py @@ -34,7 +34,7 @@ test_count_file.write(str(test_count)) 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') @@ -148,7 +148,6 @@ class StreamTest(unittest.TestCase): class UserTests(unittest.TestCase): def testHandleSeparatorRaisingExceptions(self): - user = diaspy.people.User(test_connection) handles = ['user.pod.example.com', 'user@podexamplecom', '@pod.example.com', @@ -156,24 +155,33 @@ class UserTests(unittest.TestCase): '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) @@ -218,4 +226,9 @@ class NotificationsTests(unittest.TestCase): 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!') -- 2.25.1