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