Small changes in connection
[diaspy.git] / diaspy / connection.py
CommitLineData
a29d3526
MM
1#!/usr/bin/env python
2
8993810c
MM
3import re
4import requests
7a31b4aa 5import json
ff3d2ab4 6import warnings
8993810c 7
a29d3526 8
178faa46
MM
9"""This module abstracts connection to pod.
10"""
11
12
385e7ebe
MM
13class LoginError(Exception):
14 pass
15
16
b0b4c46d
MM
17class TokenError(Exception):
18 pass
19
20
a29d3526 21class Connection():
1cff2093 22 """Object representing connection with the pod.
a29d3526 23 """
27a28aaf
MM
24 _token_regex = re.compile(r'content="(.*?)"\s+name="csrf-token')
25 _userinfo_regex = re.compile(r'window.current_user_attributes = ({.*})')
27a28aaf 26
615edb73 27 def __init__(self, pod, username='', password='', schema='https'):
8993810c
MM
28 """
29 :param pod: The complete url of the diaspora pod to use.
30 :type pod: str
31 :param username: The username used to log in.
32 :type username: str
33 :param password: The password used to log in.
34 :type password: str
35 """
a29d3526 36 self.pod = pod
8993810c 37 self.session = requests.Session()
6cd1bae0
MM
38 self.login_data = {}
39 self.token = ''
615edb73 40 try: self._setlogin(username, password)
75a456f4
MM
41 except requests.exceptions.MissingSchema:
42 self.pod = '{0}://{1}'.format(schema, self.pod)
43 warnings.warn('schema was missing')
615edb73 44 finally: pass
75a456f4 45 try: self._setlogin(username, password)
615edb73 46 except Exception as e: raise LoginError('cannot create login data (caused by: {0})'.format(e))
8993810c 47
d4bd92cd
MM
48 def __repr__(self):
49 """Returns token string.
50 It will be easier to change backend if programs will just use:
51 repr(connection)
52 instead of calling a specified method.
53 """
54 return self.get_token()
55
33b34938 56 def get(self, string, headers={}, params={}):
8993810c
MM
57 """This method gets data from session.
58 Performs additional checks if needed.
59
60 Example:
7a31b4aa 61 To obtain 'foo' from pod one should call `get('foo')`.
8993810c
MM
62
63 :param string: URL to get without the pod's URL and slash eg. 'stream'.
64 :type string: str
65 """
33b34938 66 return self.session.get('{0}/{1}'.format(self.pod, string), params=params, headers=headers)
8993810c
MM
67
68 def post(self, string, data, headers={}, params={}):
69 """This method posts data to session.
70 Performs additional checks if needed.
71
72 Example:
7a31b4aa 73 To post to 'foo' one should call `post('foo', data={})`.
8993810c
MM
74
75 :param string: URL to post without the pod's URL and slash eg. 'status_messages'.
76 :type string: str
77 :param data: Data to post.
78 :param headers: Headers (optional).
79 :type headers: dict
80 :param params: Parameters (optional).
81 :type params: dict
82 """
83 string = '{0}/{1}'.format(self.pod, string)
75fcdd7d 84 request = self.session.post(string, data, headers=headers, params=params)
8993810c
MM
85 return request
86
178faa46
MM
87 def put(self, string, data=None, headers={}, params={}):
88 """This method PUTs to session.
89 """
90 string = '{0}/{1}'.format(self.pod, string)
91 if data is not None: request = self.session.put(string, data, headers=headers, params=params)
92 else: request = self.session.put(string, headers=headers, params=params)
93 return request
94
8993810c
MM
95 def delete(self, string, data, headers={}):
96 """This method lets you send delete request to session.
97 Performs additional checks if needed.
98
99 :param string: URL to use.
100 :type string: str
101 :param data: Data to use.
102 :param headers: Headers to use (optional).
103 :type headers: dict
104 """
105 string = '{0}/{1}'.format(self.pod, string)
75fcdd7d 106 request = self.session.delete(string, data=data, headers=headers)
8993810c
MM
107 return request
108
109 def _setlogin(self, username, password):
110 """This function is used to set data for login.
b0b4c46d 111
8993810c
MM
112 .. note::
113 It should be called before _login() function.
114 """
a29d3526 115 self.username, self.password = username, password
8993810c
MM
116 self.login_data = {'user[username]': self.username,
117 'user[password]': self.password,
b0b4c46d 118 'authenticity_token': self._fetchtoken()}
8993810c 119
385e7ebe
MM
120 def _login(self):
121 """Handles actual login request.
122 Raises LoginError if login failed.
123 """
124 request = self.post('users/sign_in',
125 data=self.login_data,
126 headers={'accept': 'application/json'})
127 if request.status_code != 201:
b0b4c46d 128 raise LoginError('{0}: login failed'.format(request.status_code))
385e7ebe
MM
129
130 def login(self, username='', password=''):
131 """This function is used to log in to a pod.
132 Will raise LoginError if password or username was not specified.
8993810c 133 """
385e7ebe
MM
134 if username and password: self._setlogin(username, password)
135 if not self.username or not self.password: raise LoginError('password or username not specified')
136 self._login()
137
63cc182d
MM
138 def logout(self):
139 """Logs out from a pod.
140 When logged out you can't do anything.
141 """
142 self.get('users/sign_out')
b0b4c46d
MM
143 self.username = ''
144 self.token = ''
145 self.password = ''
63cc182d 146
385e7ebe 147 def podswitch(self, pod):
7a31b4aa 148 """Switches pod from current to another one.
385e7ebe
MM
149 """
150 self.pod = pod
151 self._login()
8993810c 152
7a31b4aa
MM
153 def getUserInfo(self):
154 """This function returns the current user's attributes.
155
156 :returns: dict -- json formatted user info.
157 """
158 request = self.get('bookmarklet')
1cff2093
MM
159 try:
160 userdata = json.loads(self._userinfo_regex.search(request.text).group(1))
161 except AttributeError:
162 raise errors.DiaspyError('cannot find user data')
7a31b4aa
MM
163 return userdata
164
b0b4c46d
MM
165 def _fetchtoken(self):
166 """This method tries to get token string needed for authentication on D*.
167
168 :returns: token string
169 """
170 request = self.get('stream')
171 token = self._token_regex.search(request.text).group(1)
6d8d47ce 172 self.token = token
b0b4c46d 173 return token
2ec93347 174
6d8d47ce 175 def get_token(self, fetch=False):
385e7ebe 176 """This function returns a token needed for authentication in most cases.
b0b4c46d
MM
177 Each time it is run a _fetchtoken() is called and refreshed token is stored.
178
179 It is more safe to use than _fetchtoken().
ff3d2ab4
MM
180 By setting new you can request new token or decide to get stored one.
181 If no token is stored new one will be fatched anyway.
a29d3526 182
8993810c
MM
183 :returns: string -- token used to authenticate
184 """
b0b4c46d 185 try:
6d8d47ce
MM
186 if fetch: self._fetchtoken()
187 if not self.token: self._fetchtoken()
b0b4c46d 188 except requests.exceptions.ConnectionError as e:
ff3d2ab4 189 warnings.warn('{0} was cought: reusing old token'.format(e))
b0b4c46d 190 finally:
ff3d2ab4
MM
191 if not self.token: raise TokenError('cannot obtain token and no previous token found for reuse')
192 return self.token