Some fixes to make diaspy more Python 2.x-friendly
[diaspy.git] / diaspy / connection.py
CommitLineData
a29d3526 1#!/usr/bin/env python
e2c48b2f 2# -*- coding: utf-8 -*-
a29d3526 3
36f274c0
MM
4
5"""This module abstracts connection to pod.
6"""
7
8
b74513c5 9import json
8993810c
MM
10import re
11import requests
ff3d2ab4 12import warnings
8993810c 13
2cf8467c
MM
14from diaspy import errors
15
a29d3526 16
36f274c0 17DEBUG = True
178faa46
MM
18
19
a29d3526 20class Connection():
1cff2093 21 """Object representing connection with the pod.
a29d3526 22 """
27a28aaf 23 _token_regex = re.compile(r'content="(.*?)"\s+name="csrf-token')
39af9756
MM
24 _userinfo_regex = re.compile(r'window.current_user_attributes = ({.*})')
25 # this is for older version of D*
26 _userinfo_regex_2 = re.compile(r'gon.user=({.*});gon.preloads')
27a28aaf 27
36f274c0 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
2cf8467c 38 self._session = requests.Session()
36f274c0 39 self._login_data = {'user[remember_me]': 1, 'utf8': '✓'}
5de52803 40 self._userdata = {}
2cf8467c 41 self._token = ''
5de52803 42 self._diaspora_session = ''
36f274c0 43 self._cookies = self._fetchcookies()
2cf8467c 44 try:
36f274c0
MM
45 #self._setlogin(username, password)
46 self._login_data = {'user[username]': username,
47 'user[password]': password,
48 'authenticity_token': self._fetchtoken()}
49 success = True
75a456f4
MM
50 except requests.exceptions.MissingSchema:
51 self.pod = '{0}://{1}'.format(schema, self.pod)
52 warnings.warn('schema was missing')
36f274c0 53 success = False
2cf8467c
MM
54 finally:
55 pass
56 try:
36f274c0
MM
57 if not success:
58 self._login_data = {'user[username]': username,
59 'user[password]': password,
60 'authenticity_token': self._fetchtoken()}
2cf8467c
MM
61 except Exception as e:
62 raise errors.LoginError('cannot create login data (caused by: {0})'.format(e))
8993810c 63
36f274c0
MM
64 def _fetchcookies(self):
65 request = self.get('stream')
66 return request.cookies
67
d4bd92cd
MM
68 def __repr__(self):
69 """Returns token string.
70 It will be easier to change backend if programs will just use:
71 repr(connection)
72 instead of calling a specified method.
73 """
cf6a800f 74 return self._fetchtoken()
d4bd92cd 75
36f274c0 76 def get(self, string, headers={}, params={}, direct=False, **kwargs):
8993810c
MM
77 """This method gets data from session.
78 Performs additional checks if needed.
79
80 Example:
7a31b4aa 81 To obtain 'foo' from pod one should call `get('foo')`.
8993810c
MM
82
83 :param string: URL to get without the pod's URL and slash eg. 'stream'.
84 :type string: str
73a9e0d3
MM
85 :param direct: if passed as True it will not be expanded
86 :type direct: bool
8993810c 87 """
73a9e0d3
MM
88 if not direct: url = '{0}/{1}'.format(self.pod, string)
89 else: url = string
36f274c0 90 return self._session.get(url, params=params, headers=headers, **kwargs)
8993810c 91
36f274c0 92 def post(self, string, data, headers={}, params={}, **kwargs):
8993810c
MM
93 """This method posts data to session.
94 Performs additional checks if needed.
95
96 Example:
7a31b4aa 97 To post to 'foo' one should call `post('foo', data={})`.
8993810c
MM
98
99 :param string: URL to post without the pod's URL and slash eg. 'status_messages'.
100 :type string: str
101 :param data: Data to post.
102 :param headers: Headers (optional).
103 :type headers: dict
104 :param params: Parameters (optional).
105 :type params: dict
106 """
107 string = '{0}/{1}'.format(self.pod, string)
36f274c0 108 request = self._session.post(string, data, headers=headers, params=params, **kwargs)
8993810c
MM
109 return request
110
36f274c0 111 def put(self, string, data=None, headers={}, params={}, **kwargs):
178faa46
MM
112 """This method PUTs to session.
113 """
114 string = '{0}/{1}'.format(self.pod, string)
36f274c0
MM
115 if data is not None: request = self._session.put(string, data, headers=headers, params=params, **kwargs)
116 else: request = self._session.put(string, headers=headers, params=params, **kwargs)
178faa46
MM
117 return request
118
36f274c0 119 def delete(self, string, data, headers={}, **kwargs):
8993810c
MM
120 """This method lets you send delete request to session.
121 Performs additional checks if needed.
122
123 :param string: URL to use.
124 :type string: str
125 :param data: Data to use.
126 :param headers: Headers to use (optional).
127 :type headers: dict
128 """
129 string = '{0}/{1}'.format(self.pod, string)
36f274c0 130 request = self._session.delete(string, data=data, headers=headers, **kwargs)
8993810c
MM
131 return request
132
133 def _setlogin(self, username, password):
134 """This function is used to set data for login.
b0b4c46d 135
8993810c
MM
136 .. note::
137 It should be called before _login() function.
138 """
2cf8467c
MM
139 self._login_data = {'user[username]': username,
140 'user[password]': password,
141 'authenticity_token': self._fetchtoken()}
8993810c 142
385e7ebe
MM
143 def _login(self):
144 """Handles actual login request.
145 Raises LoginError if login failed.
146 """
147 request = self.post('users/sign_in',
36f274c0
MM
148 data=self._login_data,
149 allow_redirects=False)
150 if request.status_code != 302:
2cf8467c 151 raise errors.LoginError('{0}: login failed'.format(request.status_code))
385e7ebe 152
36f274c0 153 def login(self, remember_me=1):
385e7ebe
MM
154 """This function is used to log in to a pod.
155 Will raise LoginError if password or username was not specified.
8993810c 156 """
2cf8467c
MM
157 if not self._login_data['user[username]'] or not self._login_data['user[password]']:
158 raise errors.LoginError('username and/or password is not specified')
36f274c0
MM
159 self._login_data['user[remember_me]'] = remember_me
160 status = self._login()
2cf8467c 161 self._login_data = {}
f3b7fd44 162 return self
385e7ebe 163
63cc182d
MM
164 def logout(self):
165 """Logs out from a pod.
166 When logged out you can't do anything.
167 """
168 self.get('users/sign_out')
b0b4c46d 169 self.token = ''
63cc182d 170
2cf8467c 171 def podswitch(self, pod, username, password):
7a31b4aa 172 """Switches pod from current to another one.
385e7ebe
MM
173 """
174 self.pod = pod
2cf8467c 175 self._setlogin(username, password)
385e7ebe 176 self._login()
8993810c 177
b0b4c46d
MM
178 def _fetchtoken(self):
179 """This method tries to get token string needed for authentication on D*.
180
181 :returns: token string
182 """
183 request = self.get('stream')
184 token = self._token_regex.search(request.text).group(1)
2cf8467c 185 self._token = token
b0b4c46d 186 return token
2ec93347 187
2b6b487e 188 def get_token(self, fetch=True):
385e7ebe 189 """This function returns a token needed for authentication in most cases.
2cf8467c 190 **Notice:** using repr() is recommended method for getting token.
a29d3526 191
b0b4c46d
MM
192 Each time it is run a _fetchtoken() is called and refreshed token is stored.
193
194 It is more safe to use than _fetchtoken().
ff3d2ab4 195 By setting new you can request new token or decide to get stored one.
2b6b487e 196 If no token is stored new one will be fetched anyway.
a29d3526 197
8993810c
MM
198 :returns: string -- token used to authenticate
199 """
b0b4c46d 200 try:
36f274c0 201 if fetch or not self._token: self._fetchtoken()
b0b4c46d 202 except requests.exceptions.ConnectionError as e:
ff3d2ab4 203 warnings.warn('{0} was cought: reusing old token'.format(e))
b0b4c46d 204 finally:
2cf8467c
MM
205 if not self._token: raise errors.TokenError('cannot obtain token and no previous token found for reuse')
206 return self._token
5de52803
MM
207
208 def getSessionToken(self):
209 """Returns session token string (_diaspora_session).
210 """
211 return self._diaspora_session
39af9756
MM
212
213 def getUserData(self):
214 """Returns user data.
215 """
216 request = self.get('bookmarklet')
217 userdata = self._userinfo_regex.search(request.text)
218 if userdata is None: userdata = self._userinfo_regex_2.search(request.text)
219 if userdata is None: raise errors.DiaspyError('cannot find user data')
220 userdata = userdata.group(1)
221 return json.loads(userdata)