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