`diaspy` ported to use `Connection()` object
authorMarek Marecki <triviuss@gmail.com>
Sat, 13 Apr 2013 23:10:34 +0000 (01:10 +0200)
committerMarek Marecki <triviuss@gmail.com>
Sat, 13 Apr 2013 23:10:34 +0000 (01:10 +0200)
Makefile [new file with mode: 0644]
diaspy/__init__.py
diaspy/client.py
diaspy/connection.py
diaspy/conversations.py
diaspy/models.py
manual/connection.mdown [new file with mode: 0644]
tests.py

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..e9a501a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+.PHONY: style-check test
+
+style-check:
+       flake8 ./diaspy/
+
+test:
+       python3 -m unittest --verbose --catch --failfast tests.py
index 25b3822f234cf6bcc547bb64d83858cee7b5c02c..7666c3535479f6fbeca51f191416e07b63582803 100644 (file)
@@ -1,3 +1,3 @@
-import diaspy.client
-import diaspy.models
-import diaspy.conversations
+from diaspy import client
+from diaspy import models
+from diaspy import conversations
index 337b6be99ee535edf204ea397df0e48c924f2a53..8a1a7ad41e736fb8bd21d629164fa24ad8eb871f 100644 (file)
@@ -1,4 +1,3 @@
-import requests
 import re
 import json
 import diaspy.models
@@ -20,51 +19,6 @@ class Client:
         self.connection = diaspy.connection.Connection(pod, username, password)
         self.connection.login()
         self.pod = pod
-        self._post_data = {}
-
-    def _sessionget(self, string):
-        """This method gets data from session.
-        Performs additional checks if needed.
-
-        Example:
-            To obtain 'foo' from pod one should call `_sessionget('foo')`.
-
-        :param string: URL to get without the pod's URL and slash eg. 'stream'.
-        :type string: str
-        """
-        request = self.connection.get(string)
-        return request
-
-    def _sessionpost(self, string, data, headers={}, params={}):
-        """This method posts data to session.
-        Performs additional checks if needed.
-
-        Example:
-            To post to 'foo' one should call `_sessionpost('foo', data={})`.
-
-        :param string: URL to post without the pod's URL and slash eg. 'status_messages'.
-        :type string: str
-        :param data: Data to post.
-        :param headers: Headers (optional).
-        :type headers: dict
-        :param params: Parameters (optional).
-        :type params: dict
-        """
-        request = self.connection.post(string, data, headers, params)
-        return request
-
-    def _sessiondelete(self, string, data, headers={}):
-        """This method lets you send delete request to session.
-        Performs additional checks if needed.
-
-        :param string: URL to use.
-        :type string: str
-        :param data: Data to use.
-        :param headers: Headers to use (optional).
-        :type headers: dict
-        """
-        request = self.connection.delete(string, data, headers)
-        return request
 
     def _setpostdata(self, text, aspect_ids, photos):
         """This function prepares data for posting.
@@ -87,10 +41,10 @@ class Client:
         :returns: diaspy.models.Post -- the Post which has been created
         """
         r = self.connection.post('status_messages',
-                              data=json.dumps(self._post_data),
-                              headers={'content-type': 'application/json',
-                                       'accept': 'application/json',
-                                       'x-csrf-token': self.get_token()})
+                                 data=json.dumps(self._post_data),
+                                 headers={'content-type': 'application/json',
+                                          'accept': 'application/json',
+                                          'x-csrf-token': self.get_token()})
         if r.status_code != 201:
             raise Exception('{0}: Post could not be posted.'.format(
                             r.status_code))
@@ -156,7 +110,7 @@ class Client:
             raise Exception('wrong status code: {0}'.format(request.status_code))
 
         stream = request.json()
-        return [diaspy.models.Post(str(post['id']), self) for post in stream]
+        return [diaspy.models.Post(str(post['id']), self.connection) for post in stream]
 
     def get_notifications(self):
         """This functions returns a list of notifications.
@@ -183,7 +137,7 @@ class Client:
             raise Exception('wrong status code: {0}'.format(r.status_code))
 
         mentions = r.json()
-        return [diaspy.models.Post(str(post['id']), self) for post in mentions]
+        return [diaspy.models.Post(str(post['id']), self.connection) for post in mentions]
 
     def get_tag(self, tag):
         """This functions returns a list of posts containing the tag.
@@ -198,7 +152,7 @@ class Client:
             raise Exception('wrong status code: {0}'.format(r.status_code))
 
         tagged_posts = r.json()
-        return [diaspy.models.Post(str(post['id']), self) for post in tagged_posts]
+        return [diaspy.models.Post(str(post['id']), self.connection) for post in tagged_posts]
 
     def get_mailbox(self):
         """This functions returns a list of messages found in the conversation.
@@ -211,7 +165,7 @@ class Client:
             raise Exception('wrong status code: {0}'.format(r.status_code))
 
         mailbox = r.json()
-        return [diaspy.conversations.Conversation(str(conversation['conversation']['id']), self)
+        return [diaspy.conversations.Conversation(str(conversation['conversation']['id']), self.connection)
                 for conversation in mailbox]
 
     def add_user_to_aspect(self, user_id, aspect_id):
@@ -260,7 +214,7 @@ class Client:
                 'person_id': user_id}
 
         r = self.connection.delete('aspect_memberships/42.json',
-                                data=data)
+                                   data=data)
 
         if r.status_code != 200:
             raise Exception('wrong status code: {0}'.format(r.status_code))
@@ -273,7 +227,7 @@ class Client:
         data = {'authenticity_token': self.get_token()}
 
         r = self.connection.delete('aspects/{}'.format(aspect_id),
-                                data=data)
+                                   data=data)
 
         if r.status_code != 404:
             raise Exception('wrong status code: {0}'.format(r.status_code))
@@ -295,8 +249,8 @@ class Client:
                 'authenticity_token': self.get_token()}
 
         r = self.connection.post('conversations/',
-                              data=data,
-                              headers={'accept': 'application/json'})
+                                 data=data,
+                                 headers={'accept': 'application/json'})
         if r.status_code != 200:
             raise Exception('{0}: Conversation could not be started.'
                             .format(r.status_code))
index 1abcfc34bd3faaf4ade314ff123f0b00a2dc68ca..585089caa362933bd52c08f56e8feddc91cb7e68 100644 (file)
@@ -4,6 +4,10 @@ import re
 import requests
 
 
+class LoginError(Exception):
+    pass
+
+
 class Connection():
     """Object representing connection with the server.
     It is pushed around internally and is considered private.
@@ -87,17 +91,32 @@ class Connection():
                            'user[password]': self.password,
                            'authenticity_token': self.getToken()}
 
-    def login(self):
-        """This function is used to connect to the pod and log in.
+    def _login(self):
+        """Handles actual login request.
+        Raises LoginError if login failed.
+        """
+        request = self.post('users/sign_in',
+                            data=self.login_data,
+                            headers={'accept': 'application/json'})
+        if request.status_code != 201:
+            raise Exception('{0}: Login failed.'.format(request.status_code))
+
+    def login(self, username='', password=''):
+        """This function is used to log in to a pod.
+        Will raise LoginError if password or username was not specified.
         """
-        r = self.post('users/sign_in',
-                      data=self.login_data,
-                      headers={'accept': 'application/json'})
-        if r.status_code != 201:
-            raise Exception('{0}: Login failed.'.format(r.status_code))
+        if username and password: self._setlogin(username, password)
+        if not self.username or not self.password: raise LoginError('password or username not specified')
+        self._login()
+
+    def podswitch(self, pod):
+        """Switches pod.
+        """
+        self.pod = pod
+        self._login()
 
     def getToken(self):
-        """This function gets a token needed for authentication in most cases
+        """This function returns a token needed for authentication in most cases.
 
         :returns: string -- token used to authenticate
         """
index 72e13db496cc431db064abbcf778f27c427c2dd6..fc2218d2b67b495693e3b13264c047acf5798aef 100644 (file)
@@ -1,12 +1,11 @@
 #!/usr/bin/env python3
 
 
-class Conversation:
+class Conversation():
     """This class represents a conversation.
 
     .. note::
         Remember that you need to have access to the conversation.
-
     """
     def __init__(self, conv_id, connection):
         """
@@ -45,8 +44,8 @@ class Conversation:
                 'authenticity_token': self._connection.get_token()}
 
         r = self._connection.post('conversations/{}/messages'.format(self.conv_id),
-                                      data=data,
-                                      headers={'accept': 'application/json'})
+                                  data=data,
+                                  headers={'accept': 'application/json'})
         if r.status_code != 200:
             raise Exception('{0}: Answer could not be posted.'
                             .format(r.status_code))
@@ -59,9 +58,9 @@ class Conversation:
         data = {'authenticity_token': self._connection.get_token()}
 
         r = self._connection.delete('conversations/{0}/visibility/'
-                                        .format(self.conv_id),
-                                        data=data,
-                                        headers={'accept': 'application/json'})
+                                    .format(self.conv_id),
+                                    data=data,
+                                    headers={'accept': 'application/json'})
 
         if r.status_code != 404:
             raise Exception('{0}: Conversation could not be deleted.'
index 7eaea06c0f842c323388ade778754831bc1b480f..27b762dad4678841ef9c7adad19856a104761fa7 100644 (file)
@@ -36,8 +36,8 @@ class Post:
         data = {'authenticity_token': self._connection.getToken()}
 
         r = self._connection.post('posts/{0}/likes'.format(self.post_id),
-                                      data=data,
-                                      headers={'accept': 'application/json'})
+                                  data=data,
+                                  headers={'accept': 'application/json'})
 
         if r.status_code != 201:
             raise Exception('{0}: Post could not be liked.'
@@ -53,10 +53,10 @@ class Post:
         post_data = self.get_data()
 
         r = self._connection.delete('posts/{0}/likes/{1}'
-                                        .format(self.post_id,
-                                                post_data['interactions']
-                                                         ['likes'][0]['id']),
-                                        data=data)
+                                    .format(self.post_id,
+                                            post_data['interactions']
+                                                     ['likes'][0]['id']),
+                                    data=data)
 
         if r.status_code != 204:
             raise Exception('{0}: Like could not be removed.'
@@ -72,8 +72,8 @@ class Post:
                 'authenticity_token': self._connection.getToken()}
 
         r = self._connection.post('reshares',
-                                      data=data,
-                                      headers={'accept': 'application/json'})
+                                  data=data,
+                                  headers={'accept': 'application/json'})
 
         if r.status_code != 201:
             raise Exception('{0}: Post could not be reshared.'
@@ -92,8 +92,8 @@ class Post:
                 'authenticity_token': self._connection.getToken()}
 
         r = self._connection.post('posts/{0}/comments'.format(self.post_id),
-                                      data=data,
-                                      headers={'accept': 'application/json'})
+                                  data=data,
+                                  headers={'accept': 'application/json'})
 
         if r.status_code != 201:
             raise Exception('{0}: Comment could not be posted.'
@@ -111,10 +111,10 @@ class Post:
         data = {'authenticity_token': self._connection.getToken()}
 
         r = self._connection.delete('posts/{0}/comments/{1}'
-                                        .format(self.post_id,
-                                                comment_id),
-                                        data=data,
-                                        headers={'accept': 'application/json'})
+                                    .format(self.post_id,
+                                            comment_id),
+                                    data=data,
+                                    headers={'accept': 'application/json'})
 
         if r.status_code != 204:
             raise Exception('{0}: Comment could not be deleted.'
@@ -125,7 +125,7 @@ class Post:
         """
         data = {'authenticity_token': self._connection.getToken()}
         r = self._connection.delete('posts/{0}'.format(self.post_id),
-                                        data=data,
-                                        headers={'accept': 'application/json'})
+                                    data=data,
+                                    headers={'accept': 'application/json'})
         if r.status_code != 204:
             raise Exception('{0}: Post could not be deleted'.format(r.status_code))
diff --git a/manual/connection.mdown b/manual/connection.mdown
new file mode 100644 (file)
index 0000000..f5219a9
--- /dev/null
@@ -0,0 +1,82 @@
+#### `Connection()` object
+
+This is the object that is used by `diaspy`'s internals. 
+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,
+
+
+`Connection()` is the most low-level part of `diaspy` and provides everything 
+what is needed to talk to a pod.
+
+However, using only `Connection()` would be hard and cumberstone so there are 
+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 
+you gave it valid pod, username and password. 
+
+On the other hand, `Connection()` is more stupid and it will not log you in unless 
+you explicitly order it to do so. 
+Logging in with `Connection()` is done via `login()` method. 
+
+**Example:**
+
+    connection = diaspy.connection.Connection(pod='https://pod.example.com')
+    connection.login('user', 'password')
+
+    OR
+
+    connection = diaspy.connection.Connection(pod='https://pod.example.com',
+                                              username='user',
+                                              password='password')
+    connection.login()
+
+
+In the example above two ways of logging in were shown. 
+In the first one only *pod* is passed to the object and 
+*username* and *password* were passed to `login()` method. 
+
+In the second one everything is passed directly to the object being 
+created and `login()` is called without any arguments. 
+
+Both ways are valid and will result in exactly the same connection. 
+But consider the following example:
+
+
+    connection = diaspy.connection.Connection(pod='https://pod.example.com',
+                                              username='user',
+                                              password='password')
+    connection.login(username='loser', password='passphrase')
+
+This code will result in connection with username `loser` and 
+password `passphrase` because data passed to `login()` overrides data 
+passed directly to object. 
+
+**Remember:** if you pass something to `login()` it will not only *override* but 
+also *overwrite* the username and password!
+
+
+----
+
+##### Switching pods
+
+`Connection()` provides functionality for switching pods on the fly. 
+This can be achieved with `podswitch()` method. 
+
+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.
+
+
+----
+
+If you want to write your own interface or client for D\* 
+`Connection()` will be the only object you need.
+
index 3030510a411ea054d334c306a955a1cd8a9cc475..f81538ffb38ebc66207c35fc5d37b6fe18aaddee 100644 (file)
--- a/tests.py
+++ b/tests.py
@@ -17,6 +17,15 @@ __username__ = testconf.__username__
 __passwd__ = testconf.__passwd__
 
 
+class ConnectionTest(unittest.TestCase):
+    def testLoginWithoutUsername(self):
+        connection = diaspy.connection.Connection(pod=__pod__)
+        self.assertRaises(diaspy.connection.LoginError, connection.login, password='foo')
+
+    def testLoginWithoutPassword(self):
+        connection = diaspy.connection.Connection(pod=__pod__)
+        self.assertRaises(diaspy.connection.LoginError, connection.login, username='user')
+
 class ClientTests(unittest.TestCase):
     def testGettingUserInfo(self):
         client = diaspy.client.Client(__pod__, __username__, __passwd__)