| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,14 +23,12 @@ _:py:class:`webuntis.session.Session`. Unless noted otherwise, they get saved | |||
| 23 | 23 | in a dictionary located in the instance's ``options`` attribute and can be | |
| 24 | 24 | modified afterwards. | |
| 25 | 25 | ||
| 26 | - - ``credentials``: A dictionary containing ``username`` and ``password``. Before | ||
| 27 | - the session is used, :py:meth:`webuntis.session.JSONRPCSession.login` must | ||
| 28 | - be called, in order to add a ``jsessionid`` key, which will be deleted when | ||
| 29 | - calling :py:meth:`webuntis.session.JSONRPCSession.logout`. | ||
| 30 | - | ||
| 31 | - In theory, you can obtain the jsessionid yourself and add it to the | ||
| 32 | - ``credentials`` dictionary. In this case, the other two keys are obviously | ||
| 33 | - not needed. | ||
| 26 | + - ``username``: The username used for the API. | ||
| 27 | + | ||
| 28 | + - ``password``: The password used for the API. | ||
| 29 | + | ||
| 30 | + - ``jsessionid``: The current session key. Shouldn't be changed unless you | ||
| 31 | + know what you're doing. | ||
| 34 | 32 | ||
| 35 | 33 | - ``school``: A string containing a valid school name. | |
| 36 | 34 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,6 +25,112 @@ | |||
| 25 | 25 | import simplejson as json # from dependency "simplejson" | |
| 26 | 26 | ||
| 27 | 27 | ||
| 28 | + class JSONRPCRequest(object): | ||
| 29 | + def __init__(self, session, method, params=None): | ||
| 30 | + self._session = session | ||
| 31 | + self._method = method | ||
| 32 | + self._params = params or {} | ||
| 33 | + | ||
| 34 | + def request(self): | ||
| 35 | + data = None | ||
| 36 | + i = 0 | ||
| 37 | + while data is None and i <= self._session.options['login_repeat']: | ||
| 38 | + i += 1 | ||
| 39 | + try: | ||
| 40 | + data = self._make_request() | ||
| 41 | + except errors.NotLoggedInError as e: | ||
| 42 | + if self._session.options['login_repeat'] > 0: | ||
| 43 | + self._session.logout(suppress_errors=True) | ||
| 44 | + self._session.login() | ||
| 45 | + else: | ||
| 46 | + raise e | ||
| 47 | + | ||
| 48 | + return data | ||
| 49 | + | ||
| 50 | + def _make_request(self): | ||
| 51 | + ''' | ||
| 52 | + A method for sending a JSON-RPC request. | ||
| 53 | + | ||
| 54 | + :param method: The JSON-RPC method to be executed | ||
| 55 | + :type method: str | ||
| 56 | + | ||
| 57 | + :param params: JSON-RPC parameters to the method (should be JSON | ||
| 58 | + serializable) | ||
| 59 | + :type params: dict | ||
| 60 | + ''' | ||
| 61 | + | ||
| 62 | + def _handle_json_error(): | ||
| 63 | + '''A helper function for handling JSON error codes.''' | ||
| 64 | + logging.error(res_data) | ||
| 65 | + try: | ||
| 66 | + error = res_data['error'] | ||
| 67 | + exc = self._errorcodes[error['code']](error['message']) | ||
| 68 | + except KeyError: | ||
| 69 | + exc = errors.RemoteError( | ||
| 70 | + 'Some JSON-RPC-ish error happened. Please report this to the \ | ||
| 71 | + developer so he can implement a proper handling.', | ||
| 72 | + str(res_data), | ||
| 73 | + str(req_data) | ||
| 74 | + ) | ||
| 75 | + | ||
| 76 | + raise exc | ||
| 77 | + | ||
| 78 | + url = self._session.options['server'] | ||
| 79 | + url += '?school=' + self._session.options['school'] | ||
| 80 | + cookie_header = True | ||
| 81 | + if self._method == 'authenticate': | ||
| 82 | + cookie_header = False | ||
| 83 | + elif 'jsessionid' not in self._session.options: | ||
| 84 | + raise errors.AuthError('Don\'t have JSESSIONID. Did you already log out?') | ||
| 85 | + | ||
| 86 | + req_data = { | ||
| 87 | + 'id': str(datetime.datetime.today()), | ||
| 88 | + 'method': self._method, | ||
| 89 | + 'params': self._params, | ||
| 90 | + 'jsonrpc': '2.0' | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + req_data_json = json.dumps(req_data).encode() | ||
| 94 | + | ||
| 95 | + logging.debug('Making new request:') | ||
| 96 | + logging.debug('URL: ' + url) | ||
| 97 | + logging.debug(req_data_json) | ||
| 98 | + | ||
| 99 | + req = urlrequest.Request( | ||
| 100 | + url, | ||
| 101 | + req_data_json, | ||
| 102 | + { | ||
| 103 | + 'User-Agent': self._session.options['useragent'], | ||
| 104 | + 'Content-Type': 'application/json' | ||
| 105 | + } | ||
| 106 | + ) | ||
| 107 | + if cookie_header: | ||
| 108 | + req.add_header( | ||
| 109 | + 'Cookie', | ||
| 110 | + 'JSESSIONID=' + self._session.options['jsessionid'] | ||
| 111 | + ) | ||
| 112 | + | ||
| 113 | + # this will eventually raise errors, e.g. if there's an unexpected http | ||
| 114 | + # status code | ||
| 115 | + res = urlrequest.urlopen(req) | ||
| 116 | + | ||
| 117 | + res_str = res.read().decode('utf-8') | ||
| 118 | + | ||
| 119 | + try: | ||
| 120 | + res_data = json.loads(res_str) | ||
| 121 | + logging.debug('Valid JSON found') | ||
| 122 | + logging.debug(res_data) | ||
| 123 | + except ValueError: | ||
| 124 | + raise errors.RemoteError('Invalid JSON', str(res_str)) | ||
| 125 | + | ||
| 126 | + if res_data['id'] != req_data['id']: | ||
| 127 | + raise errors.RemoteError('Request id was not the same as the one returned') | ||
| 128 | + elif 'result' in res_data: | ||
| 129 | + return res_data['result'] | ||
| 130 | + else: | ||
| 131 | + handle_json_error(req_data, res_data) | ||
| 132 | + | ||
| 133 | + | ||
| 28 | 134 | class JSONRPCSession(object): | |
| 29 | 135 | '''Lower-level version of :py:class:`Session`. Do not use this.''' | |
| 30 | 136 | ||
@@ -39,7 +145,7 @@ class JSONRPCSession(object): | |||
| 39 | 145 | def __init__(self, **kwargs): | |
| 40 | 146 | # The OptionStore is an extended dictionary, associating validators | |
| 41 | 147 | # and other helper methods with each key | |
| 42 | - self.options = utils.FilterDict(utils.option_utils.option_parsers) | ||
| 148 | + self.options = utils.FilterDict(utils.option_utils.options) | ||
| 43 | 149 | options = { | |
| 44 | 150 | 'server': None, | |
| 45 | 151 | 'school': None, | |
@@ -48,24 +154,17 @@ def __init__(self, **kwargs): | |||
| 48 | 154 | 'password': None, | |
| 49 | 155 | 'jsessionid': None, | |
| 50 | 156 | 'cachelen': 20, # Not implemented in LruDict | |
| 51 | - 'keep_session_alive': True | ||
| 157 | + 'login_repeat': 0 | ||
| 52 | 158 | } | |
| 53 | 159 | options.update(kwargs) | |
| 54 | 160 | ||
| 55 | - self.options.update({ | ||
| 56 | - 'server': options['server'], | ||
| 57 | - 'school': options['school'], | ||
| 58 | - 'useragent': options['useragent'], | ||
| 59 | - 'credentials': { | ||
| 60 | - 'username': options['username'], | ||
| 61 | - 'password': options['password'], | ||
| 62 | - 'jsessionid': options['jsessionid'] | ||
| 63 | - }, | ||
| 64 | - 'keep_session_alive': options['keep_session_alive'] | ||
| 65 | - }) | ||
| 66 | 161 | if options['cachelen'] > 0: | |
| 67 | 162 | self._cache = utils.LruDict(maxlen=options['cachelen']) | |
| 68 | 163 | ||
| 164 | + del options['cachelen'] | ||
| 165 | + | ||
| 166 | + self.options.update(options) | ||
| 167 | + | ||
| 69 | 168 | def __enter__(self): | |
| 70 | 169 | '''Context-manager''' | |
| 71 | 170 | return self | |
@@ -87,10 +186,10 @@ def logout(self, suppress_errors=False): | |||
| 87 | 186 | # Send a JSON-RPC 'logout' method without parameters to log out | |
| 88 | 187 | try: | |
| 89 | 188 | # aborts if we don't have creds | |
| 90 | - self.options['credentials']['jsessionid'] | ||
| 189 | + self.options['jsessionid'] | ||
| 91 | 190 | ||
| 92 | 191 | self._make_request('logout') | |
| 93 | - del self.options['credentials']['jsessionid'] | ||
| 192 | + del self.options['jsessionid'] | ||
| 94 | 193 | except KeyError: | |
| 95 | 194 | if not suppress_errors: | |
| 96 | 195 | raise errors.NotLoggedInError('We already were logged out.') | |
@@ -108,25 +207,25 @@ def login(self): | |||
| 108 | 207 | :raises: :py:class:`webuntis.errors.AuthError` | |
| 109 | 208 | ''' | |
| 110 | 209 | ||
| 111 | - if 'username' not in self.options['credentials'] \ | ||
| 112 | - or 'password' not in self.options['credentials']: | ||
| 210 | + if 'username' not in self.options \ | ||
| 211 | + or 'password' not in self.options: | ||
| 113 | 212 | raise errors.AuthError('No login data specified.') | |
| 114 | 213 | ||
| 115 | 214 | logging.debug('Trying to authenticate with username/password...') | |
| 116 | 215 | logging.debug('Username: ' + | |
| 117 | - self.options['credentials']['username'] + | ||
| 216 | + self.options['username'] + | ||
| 118 | 217 | ' Password: ' + | |
| 119 | - self.options['credentials']['password']) | ||
| 218 | + self.options['password']) | ||
| 120 | 219 | res = self._make_request('authenticate', { | |
| 121 | - 'user': self.options['credentials']['username'], | ||
| 122 | - 'password': self.options['credentials']['password'], | ||
| 220 | + 'user': self.options['username'], | ||
| 221 | + 'password': self.options['password'], | ||
| 123 | 222 | 'client': self.options['useragent'] | |
| 124 | 223 | }) | |
| 125 | 224 | logging.debug(res) | |
| 126 | 225 | if 'sessionId' in res: | |
| 127 | 226 | logging.debug('Did get a jsessionid from the server:') | |
| 128 | - self.options['credentials']['jsessionid'] = res['sessionId'] | ||
| 129 | - logging.debug(self.options['credentials']['jsessionid']) | ||
| 227 | + self.options['jsessionid'] = res['sessionId'] | ||
| 228 | + logging.debug(self.options['jsessionid']) | ||
| 130 | 229 | else: | |
| 131 | 230 | raise errors.AuthError( | |
| 132 | 231 | 'Something went wrong while authenticating', | |
@@ -136,7 +235,7 @@ def login(self): | |||
| 136 | 235 | return self | |
| 137 | 236 | ||
| 138 | 237 | def _request(self, method, params=None): | |
| 139 | - '''A wrapper for _make_request using the LRU Cache''' | ||
| 238 | + '''A wrapper for :py:class:`JSONRPCRequest` using the LRU Cache''' | ||
| 140 | 239 | ||
| 141 | 240 | if self._cache is None: | |
| 142 | 241 | return self._make_request(method, params) | |
@@ -146,6 +245,10 @@ def _request(self, method, params=None): | |||
| 146 | 245 | self._cache[key] = self._make_request(method, params) | |
| 147 | 246 | return self._cache[key] | |
| 148 | 247 | ||
| 248 | + def _make_request(self, method, params=None): | ||
| 249 | + return JSONRPCRequest(self, method, params).request() | ||
| 250 | + | ||
| 251 | + | ||
| 149 | 252 | _errorcodes = { | |
| 150 | 253 | -32601: errors.MethodNotFoundError, | |
| 151 | 254 | -8504: errors.BadCredentialsError, | |
@@ -156,96 +259,7 @@ def _request(self, method, params=None): | |||
| 156 | 259 | ||
| 157 | 260 | ||
| 158 | 261 | ||
| 159 | - def _make_request(self, method, params=None): | ||
| 160 | - ''' | ||
| 161 | - A method for sending a JSON-RPC request. | ||
| 162 | - | ||
| 163 | - :param method: The JSON-RPC method to be executed | ||
| 164 | - :type method: str | ||
| 165 | - | ||
| 166 | - :param params: JSON-RPC parameters to the method (should be JSON | ||
| 167 | - serializable) | ||
| 168 | - :type params: dict | ||
| 169 | - ''' | ||
| 170 | 262 | ||
| 171 | - def _handle_json_error(): | ||
| 172 | - '''A helper function for handling JSON error codes.''' | ||
| 173 | - logging.error(res_data) | ||
| 174 | - try: | ||
| 175 | - error = res_data['error'] | ||
| 176 | - exc = self._errorcodes[error['code']](error['message']) | ||
| 177 | - except KeyError: | ||
| 178 | - exc = errors.RemoteError( | ||
| 179 | - 'Some JSON-RPC-ish error happened. Please report this to the \ | ||
| 180 | - developer so he can implement a proper handling.', | ||
| 181 | - str(res_data), | ||
| 182 | - str(req_data) | ||
| 183 | - ) | ||
| 184 | - | ||
| 185 | - if type(exc) is errors.NotLoggedInError and self.options['keep_session_alive']: | ||
| 186 | - self.logout(suppress_errors=True) | ||
| 187 | - self.login() | ||
| 188 | - return self._make_request(method, params) | ||
| 189 | - else: | ||
| 190 | - raise exc | ||
| 191 | - | ||
| 192 | - url = self.options['server'] | ||
| 193 | - url += '?school=' + self.options['school'] | ||
| 194 | - cookie_header = True | ||
| 195 | - if method == 'authenticate': | ||
| 196 | - cookie_header = False | ||
| 197 | - elif 'jsessionid' not in self.options['credentials']: | ||
| 198 | - raise errors.AuthError('Don\'t have JSESSIONID. Did you already log out?') | ||
| 199 | - | ||
| 200 | - if not params: | ||
| 201 | - params = {} | ||
| 202 | - | ||
| 203 | - req_data = { | ||
| 204 | - 'id': str(datetime.datetime.today()), | ||
| 205 | - 'method': method, | ||
| 206 | - 'params': params, | ||
| 207 | - 'jsonrpc': '2.0' | ||
| 208 | - } | ||
| 209 | - | ||
| 210 | - req_data_json = json.dumps(req_data).encode() | ||
| 211 | - | ||
| 212 | - logging.debug('Making new request:') | ||
| 213 | - logging.debug('URL: ' + url) | ||
| 214 | - logging.debug(req_data_json) | ||
| 215 | - | ||
| 216 | - req = urlrequest.Request( | ||
| 217 | - url, | ||
| 218 | - req_data_json, | ||
| 219 | - { | ||
| 220 | - 'User-Agent': self.options['useragent'], | ||
| 221 | - 'Content-Type': 'application/json' | ||
| 222 | - } | ||
| 223 | - ) | ||
| 224 | - if cookie_header: | ||
| 225 | - req.add_header( | ||
| 226 | - 'Cookie', | ||
| 227 | - 'JSESSIONID=' + self.options['credentials']['jsessionid'] | ||
| 228 | - ) | ||
| 229 | - | ||
| 230 | - # this will eventually raise errors, e.g. if there's an unexpected http | ||
| 231 | - # status code | ||
| 232 | - res = urlrequest.urlopen(req) | ||
| 233 | - | ||
| 234 | - res_str = res.read().decode('utf-8') | ||
| 235 | - | ||
| 236 | - try: | ||
| 237 | - res_data = json.loads(res_str) | ||
| 238 | - logging.debug('Valid JSON found') | ||
| 239 | - logging.debug(res_data) | ||
| 240 | - except ValueError: | ||
| 241 | - raise errors.RemoteError('Invalid JSON', str(res_str)) | ||
| 242 | - | ||
| 243 | - if res_data['id'] != req_data['id']: | ||
| 244 | - raise errors.RemoteError('Request id was not the same as the one returned') | ||
| 245 | - elif 'result' in res_data: | ||
| 246 | - return res_data['result'] | ||
| 247 | - else: | ||
| 248 | - return handle_json_error(req_data, res_data) | ||
| 249 | 263 | ||
| 250 | 264 | ||
| 251 | 265 | class Session(JSONRPCSession): | |
| Back | FazBrowse Home | New Git URL |
0 commit comments