Update to version 0.3.0
authorMarek Marecki <triviuss@gmail.com>
Sun, 7 Jul 2013 23:44:46 +0000 (01:44 +0200)
committerMarek Marecki <triviuss@gmail.com>
Sun, 7 Jul 2013 23:44:46 +0000 (01:44 +0200)
17 files changed:
Changelog.markdown [new file with mode: 0644]
diaspy/__init__.py
diaspy/connection.py
diaspy/people.py
docs/source/conf.py
docs/source/search.rst [new file with mode: 0644]
manual/connecting_to_pod.mdown [deleted file]
manual/connection.markdown [moved from manual/connection.mdown with 64% similarity]
manual/contacts.mdown [deleted file]
manual/models.markdown [new file with mode: 0644]
manual/notifications.markdown [moved from manual/notifications.mdown with 70% similarity]
manual/people.markdown [moved from manual/people.mdown with 55% similarity]
manual/posting.markdown [moved from manual/posting.mdown with 85% similarity]
manual/search.markdown [new file with mode: 0644]
manual/streams.markdown [moved from manual/stream.mdown with 82% similarity]
setup.py
tests.py

diff --git a/Changelog.markdown b/Changelog.markdown
new file mode 100644 (file)
index 0000000..abce777
--- /dev/null
@@ -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):
+
index 0d3caa3de47f7435c3e44b9024c2918e7d17471f..d62eb86da18527bdd4e12892a1fd8330705cb6f6 100644 (file)
@@ -6,4 +6,4 @@ import diaspy.people as people
 import diaspy.notifications as notifications
 
 
-__version__ = '0.2.0'
+__version__ = '0.3.0'
index 4f70c2f1754215d55df0bab8fa4276aa9917afd9..90a2e8bb87af478eea3087f03ae195ffb58f942e 100644 (file)
@@ -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:
index 27dde8cd982d799ef930d77d1bc03f3ceecf7fb7..9eaaccd94d495477fd93a17a20079e60e70e0763 100644 (file)
@@ -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():
index b2551568a4e890b1b34ae6cfe6e14c9cc9674816..f7db2b4f9b75cc0c0a866f4ee820e79b80348f96 100644 (file)
@@ -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 (file)
index 0000000..bed54b8
--- /dev/null
@@ -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 (file)
index 5bfe50a..0000000
+++ /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
similarity index 64%
rename from manual/connection.mdown
rename to manual/connection.markdown
index 723ba82a6dc53276f673bbb75f012ed7af03bf1e..9d4e8afcd2b5d6b28118338656866c9f082ad354 100644 (file)
@@ -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 (file)
index e69de29..0000000
diff --git a/manual/models.markdown b/manual/models.markdown
new file mode 100644 (file)
index 0000000..910ca26
--- /dev/null
@@ -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`.
similarity index 70%
rename from manual/notifications.mdown
rename to manual/notifications.markdown
index 057f7aef797d4a7207fa6d063c61d58747f9d384..4312865d1331a5eb7ec67a8f5aaa0ed70c293188 100644 (file)
@@ -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
similarity index 55%
rename from manual/people.mdown
rename to manual/people.markdown
index e4563d6cdeb34a15caee989c1653e81ead9f3630..88f18e0b90dd9c1acd54c0bdeeee431f615e710e 100644 (file)
@@ -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`. 
similarity index 85%
rename from manual/posting.mdown
rename to manual/posting.markdown
index 5dff0a347e2815ba9f555d39b8a7c4e7d4dcd8d7..ca7670735927008d2dd0bc4833bdc21650dc5418 100644 (file)
@@ -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 (file)
index 0000000..69bfa9e
--- /dev/null
@@ -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.
similarity index 82%
rename from manual/stream.mdown
rename to manual/streams.markdown
index 7f21f4fd8e579e723efe55ecb488f13b0983ace2..4f2cb26312356bc669bc9b542df194d97a4fdd28 100644 (file)
@@ -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
index 8604d132c792db8602735b7d646fe14d056fcd98..e09a7097de9055ff19b54046c4df90262e8b2652 100644 (file)
--- 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']
       )
index 1302b63e13f70404151a2895ce75b3d18dcaf084..2949be93698e99dc55376d8456311c78a8bb0db4 100644 (file)
--- 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!')