Mercurial > repos > bcclaywell > argo_navis
comparison venv/lib/python2.7/site-packages/github/Requester.py @ 0:d67268158946 draft
planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
| author | bcclaywell |
|---|---|
| date | Mon, 12 Oct 2015 17:43:33 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:d67268158946 |
|---|---|
| 1 # -*- coding: utf-8 -*- | |
| 2 | |
| 3 # ########################## Copyrights and license ############################ | |
| 4 # # | |
| 5 # Copyright 2012 Andrew Bettison <andrewb@zip.com.au> # | |
| 6 # Copyright 2012 Dima Kukushkin <dima@kukushkin.me> # | |
| 7 # Copyright 2012 Michael Woodworth <mwoodworth@upverter.com> # | |
| 8 # Copyright 2012 Petteri Muilu <pmuilu@xena.(none)> # | |
| 9 # Copyright 2012 Steve English <steve.english@navetas.com> # | |
| 10 # Copyright 2012 Vincent Jacques <vincent@vincent-jacques.net> # | |
| 11 # Copyright 2012 Zearin <zearin@gonk.net> # | |
| 12 # Copyright 2013 AKFish <akfish@gmail.com> # | |
| 13 # Copyright 2013 Ed Jackson <ed.jackson@gmail.com> # | |
| 14 # Copyright 2013 Jonathan J Hunt <hunt@braincorporation.com> # | |
| 15 # Copyright 2013 Mark Roddy <markroddy@gmail.com> # | |
| 16 # Copyright 2013 Vincent Jacques <vincent@vincent-jacques.net> # | |
| 17 # # | |
| 18 # This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # | |
| 19 # # | |
| 20 # PyGithub is free software: you can redistribute it and/or modify it under # | |
| 21 # the terms of the GNU Lesser General Public License as published by the Free # | |
| 22 # Software Foundation, either version 3 of the License, or (at your option) # | |
| 23 # any later version. # | |
| 24 # # | |
| 25 # PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # | |
| 26 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # | |
| 27 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # | |
| 28 # details. # | |
| 29 # # | |
| 30 # You should have received a copy of the GNU Lesser General Public License # | |
| 31 # along with PyGithub. If not, see <http://www.gnu.org/licenses/>. # | |
| 32 # # | |
| 33 # ############################################################################## | |
| 34 | |
| 35 import logging | |
| 36 import httplib | |
| 37 import base64 | |
| 38 import urllib | |
| 39 import urlparse | |
| 40 import sys | |
| 41 import Consts | |
| 42 import re | |
| 43 | |
| 44 atLeastPython26 = sys.hexversion >= 0x02060000 | |
| 45 atLeastPython3 = sys.hexversion >= 0x03000000 | |
| 46 | |
| 47 if atLeastPython26: | |
| 48 import json | |
| 49 else: # pragma no cover (Covered by all tests with Python 2.5) | |
| 50 import simplejson as json # pragma no cover (Covered by all tests with Python 2.5) | |
| 51 | |
| 52 import GithubException | |
| 53 | |
| 54 | |
| 55 class Requester: | |
| 56 __httpConnectionClass = httplib.HTTPConnection | |
| 57 __httpsConnectionClass = httplib.HTTPSConnection | |
| 58 | |
| 59 @classmethod | |
| 60 def injectConnectionClasses(cls, httpConnectionClass, httpsConnectionClass): | |
| 61 cls.__httpConnectionClass = httpConnectionClass | |
| 62 cls.__httpsConnectionClass = httpsConnectionClass | |
| 63 | |
| 64 @classmethod | |
| 65 def resetConnectionClasses(cls): | |
| 66 cls.__httpConnectionClass = httplib.HTTPConnection | |
| 67 cls.__httpsConnectionClass = httplib.HTTPSConnection | |
| 68 | |
| 69 ############################################################# | |
| 70 # For Debug | |
| 71 @classmethod | |
| 72 def setDebugFlag(cls, flag): | |
| 73 cls.DEBUG_FLAG = flag | |
| 74 | |
| 75 @classmethod | |
| 76 def setOnCheckMe(cls, onCheckMe): | |
| 77 cls.ON_CHECK_ME = onCheckMe | |
| 78 | |
| 79 DEBUG_FLAG = False | |
| 80 | |
| 81 DEBUG_FRAME_BUFFER_SIZE = 1024 | |
| 82 | |
| 83 DEBUG_HEADER_KEY = "DEBUG_FRAME" | |
| 84 | |
| 85 ON_CHECK_ME = None | |
| 86 | |
| 87 def NEW_DEBUG_FRAME(self, requestHeader): | |
| 88 ''' | |
| 89 Initialize a debug frame with requestHeader | |
| 90 Frame count is updated and will be attached to respond header | |
| 91 The structure of a frame: [requestHeader, statusCode, responseHeader, raw_data] | |
| 92 Some of them may be None | |
| 93 ''' | |
| 94 if self.DEBUG_FLAG: # pragma no branch (Flag always set in tests) | |
| 95 new_frame = [requestHeader, None, None, None] | |
| 96 if self._frameCount < self.DEBUG_FRAME_BUFFER_SIZE - 1: # pragma no branch (Should be covered) | |
| 97 self._frameBuffer.append(new_frame) | |
| 98 else: | |
| 99 self._frameBuffer[0] = new_frame # pragma no cover (Should be covered) | |
| 100 | |
| 101 self._frameCount = len(self._frameBuffer) - 1 | |
| 102 | |
| 103 def DEBUG_ON_RESPONSE(self, statusCode, responseHeader, data): | |
| 104 ''' | |
| 105 Update current frame with response | |
| 106 Current frame index will be attached to responseHeader | |
| 107 ''' | |
| 108 if self.DEBUG_FLAG: # pragma no branch (Flag always set in tests) | |
| 109 self._frameBuffer[self._frameCount][1:4] = [statusCode, responseHeader, data] | |
| 110 responseHeader[self.DEBUG_HEADER_KEY] = self._frameCount | |
| 111 | |
| 112 def check_me(self, obj): | |
| 113 if self.DEBUG_FLAG and self.ON_CHECK_ME is not None: # pragma no branch (Flag always set in tests) | |
| 114 frame = None | |
| 115 if self.DEBUG_HEADER_KEY in obj._headers: | |
| 116 frame_index = obj._headers[self.DEBUG_HEADER_KEY] | |
| 117 frame = self._frameBuffer[frame_index] | |
| 118 self.ON_CHECK_ME(obj, frame) | |
| 119 | |
| 120 def _initializeDebugFeature(self): | |
| 121 self._frameCount = 0 | |
| 122 self._frameBuffer = [] | |
| 123 | |
| 124 ############################################################# | |
| 125 | |
| 126 def __init__(self, login_or_token, password, base_url, timeout, client_id, client_secret, user_agent, per_page): | |
| 127 self._initializeDebugFeature() | |
| 128 | |
| 129 if password is not None: | |
| 130 login = login_or_token | |
| 131 if atLeastPython3: | |
| 132 self.__authorizationHeader = "Basic " + base64.b64encode((login + ":" + password).encode("utf-8")).decode("utf-8").replace('\n', '') # pragma no cover (Covered by Authentication.testAuthorizationHeaderWithXxx with Python 3) | |
| 133 else: | |
| 134 self.__authorizationHeader = "Basic " + base64.b64encode(login + ":" + password).replace('\n', '') | |
| 135 elif login_or_token is not None: | |
| 136 token = login_or_token | |
| 137 self.__authorizationHeader = "token " + token | |
| 138 else: | |
| 139 self.__authorizationHeader = None | |
| 140 | |
| 141 self.__base_url = base_url | |
| 142 o = urlparse.urlparse(base_url) | |
| 143 self.__hostname = o.hostname | |
| 144 self.__port = o.port | |
| 145 self.__prefix = o.path | |
| 146 self.__timeout = timeout | |
| 147 self.__scheme = o.scheme | |
| 148 if o.scheme == "https": | |
| 149 self.__connectionClass = self.__httpsConnectionClass | |
| 150 elif o.scheme == "http": | |
| 151 self.__connectionClass = self.__httpConnectionClass | |
| 152 else: | |
| 153 assert False, "Unknown URL scheme" | |
| 154 self.rate_limiting = (-1, -1) | |
| 155 self.rate_limiting_resettime = 0 | |
| 156 self.FIX_REPO_GET_GIT_REF = True | |
| 157 self.per_page = per_page | |
| 158 | |
| 159 self.oauth_scopes = None | |
| 160 | |
| 161 self.__clientId = client_id | |
| 162 self.__clientSecret = client_secret | |
| 163 | |
| 164 assert user_agent is not None, 'github now requires a user-agent. ' \ | |
| 165 'See http://developer.github.com/v3/#user-agent-required' | |
| 166 self.__userAgent = user_agent | |
| 167 | |
| 168 def requestJsonAndCheck(self, verb, url, parameters=None, headers=None, input=None, cnx=None): | |
| 169 return self.__check(*self.requestJson(verb, url, parameters, headers, input, cnx)) | |
| 170 | |
| 171 def requestMultipartAndCheck(self, verb, url, parameters=None, headers=None, input=None): | |
| 172 return self.__check(*self.requestMultipart(verb, url, parameters, headers, input)) | |
| 173 | |
| 174 def __check(self, status, responseHeaders, output): | |
| 175 output = self.__structuredFromJson(output) | |
| 176 if status >= 400: | |
| 177 raise self.__createException(status, responseHeaders, output) | |
| 178 return responseHeaders, output | |
| 179 | |
| 180 def __createException(self, status, headers, output): | |
| 181 if status == 401 and output.get("message") == "Bad credentials": | |
| 182 cls = GithubException.BadCredentialsException | |
| 183 elif status == 401 and 'x-github-otp' in headers and re.match(r'.*required.*', headers['x-github-otp']): | |
| 184 cls = GithubException.TwoFactorException # pragma no cover (Should be covered) | |
| 185 elif status == 403 and output.get("message").startswith("Missing or invalid User Agent string"): | |
| 186 cls = GithubException.BadUserAgentException | |
| 187 elif status == 403 and output.get("message").startswith("API Rate Limit Exceeded"): | |
| 188 cls = GithubException.RateLimitExceededException | |
| 189 elif status == 404 and output.get("message") == "Not Found": | |
| 190 cls = GithubException.UnknownObjectException | |
| 191 else: | |
| 192 cls = GithubException.GithubException | |
| 193 return cls(status, output) | |
| 194 | |
| 195 def __structuredFromJson(self, data): | |
| 196 if len(data) == 0: | |
| 197 return None | |
| 198 else: | |
| 199 if atLeastPython3 and isinstance(data, bytes): # pragma no branch (Covered by Issue142.testDecodeJson with Python 3) | |
| 200 data = data.decode("utf-8") # pragma no cover (Covered by Issue142.testDecodeJson with Python 3) | |
| 201 try: | |
| 202 return json.loads(data) | |
| 203 except ValueError, e: | |
| 204 return {'data': data} | |
| 205 | |
| 206 def requestJson(self, verb, url, parameters=None, headers=None, input=None, cnx=None): | |
| 207 def encode(input): | |
| 208 return "application/json", json.dumps(input) | |
| 209 | |
| 210 return self.__requestEncode(cnx, verb, url, parameters, headers, input, encode) | |
| 211 | |
| 212 def requestMultipart(self, verb, url, parameters=None, headers=None, input=None): | |
| 213 def encode(input): | |
| 214 boundary = "----------------------------3c3ba8b523b2" | |
| 215 eol = "\r\n" | |
| 216 | |
| 217 encoded_input = "" | |
| 218 for name, value in input.iteritems(): | |
| 219 encoded_input += "--" + boundary + eol | |
| 220 encoded_input += "Content-Disposition: form-data; name=\"" + name + "\"" + eol | |
| 221 encoded_input += eol | |
| 222 encoded_input += value + eol | |
| 223 encoded_input += "--" + boundary + "--" + eol | |
| 224 return "multipart/form-data; boundary=" + boundary, encoded_input | |
| 225 | |
| 226 return self.__requestEncode(None, verb, url, parameters, headers, input, encode) | |
| 227 | |
| 228 def __requestEncode(self, cnx, verb, url, parameters, requestHeaders, input, encode): | |
| 229 assert verb in ["HEAD", "GET", "POST", "PATCH", "PUT", "DELETE"] | |
| 230 if parameters is None: | |
| 231 parameters = dict() | |
| 232 if requestHeaders is None: | |
| 233 requestHeaders = dict() | |
| 234 | |
| 235 self.__authenticate(url, requestHeaders, parameters) | |
| 236 requestHeaders["User-Agent"] = self.__userAgent | |
| 237 | |
| 238 url = self.__makeAbsoluteUrl(url) | |
| 239 url = self.__addParametersToUrl(url, parameters) | |
| 240 | |
| 241 encoded_input = "null" | |
| 242 if input is not None: | |
| 243 requestHeaders["Content-Type"], encoded_input = encode(input) | |
| 244 | |
| 245 self.NEW_DEBUG_FRAME(requestHeaders) | |
| 246 | |
| 247 status, responseHeaders, output = self.__requestRaw(cnx, verb, url, requestHeaders, encoded_input) | |
| 248 | |
| 249 if "x-ratelimit-remaining" in responseHeaders and "x-ratelimit-limit" in responseHeaders: | |
| 250 self.rate_limiting = (int(responseHeaders["x-ratelimit-remaining"]), int(responseHeaders["x-ratelimit-limit"])) | |
| 251 if "x-ratelimit-reset" in responseHeaders: | |
| 252 self.rate_limiting_resettime = int(responseHeaders["x-ratelimit-reset"]) | |
| 253 | |
| 254 if "x-oauth-scopes" in responseHeaders: | |
| 255 self.oauth_scopes = responseHeaders["x-oauth-scopes"].split(", ") | |
| 256 | |
| 257 self.DEBUG_ON_RESPONSE(status, responseHeaders, output) | |
| 258 | |
| 259 return status, responseHeaders, output | |
| 260 | |
| 261 def __requestRaw(self, cnx, verb, url, requestHeaders, input): | |
| 262 if cnx is None: | |
| 263 cnx = self.__createConnection() | |
| 264 else: | |
| 265 assert cnx == "status" | |
| 266 cnx = self.__httpsConnectionClass("status.github.com", 443) | |
| 267 cnx.request( | |
| 268 verb, | |
| 269 url, | |
| 270 input, | |
| 271 requestHeaders | |
| 272 ) | |
| 273 response = cnx.getresponse() | |
| 274 | |
| 275 status = response.status | |
| 276 responseHeaders = dict((k.lower(), v) for k, v in response.getheaders()) | |
| 277 output = response.read() | |
| 278 | |
| 279 cnx.close() | |
| 280 | |
| 281 self.__log(verb, url, requestHeaders, input, status, responseHeaders, output) | |
| 282 | |
| 283 return status, responseHeaders, output | |
| 284 | |
| 285 def __authenticate(self, url, requestHeaders, parameters): | |
| 286 if self.__clientId and self.__clientSecret and "client_id=" not in url: | |
| 287 parameters["client_id"] = self.__clientId | |
| 288 parameters["client_secret"] = self.__clientSecret | |
| 289 if self.__authorizationHeader is not None: | |
| 290 requestHeaders["Authorization"] = self.__authorizationHeader | |
| 291 | |
| 292 def __makeAbsoluteUrl(self, url): | |
| 293 # URLs generated locally will be relative to __base_url | |
| 294 # URLs returned from the server will start with __base_url | |
| 295 if url.startswith("/"): | |
| 296 url = self.__prefix + url | |
| 297 else: | |
| 298 o = urlparse.urlparse(url) | |
| 299 assert o.scheme == self.__scheme or o.scheme == "https" and self.__scheme == "http" # Issue #80 | |
| 300 assert o.hostname == self.__hostname | |
| 301 assert o.path.startswith(self.__prefix) | |
| 302 assert o.port == self.__port | |
| 303 url = o.path | |
| 304 if o.query != "": | |
| 305 url += "?" + o.query | |
| 306 return url | |
| 307 | |
| 308 def __addParametersToUrl(self, url, parameters): | |
| 309 if len(parameters) == 0: | |
| 310 return url | |
| 311 else: | |
| 312 return url + "?" + urllib.urlencode(parameters) | |
| 313 | |
| 314 def __createConnection(self): | |
| 315 kwds = {} | |
| 316 if not atLeastPython3: # pragma no branch (Branch useful only with Python 3) | |
| 317 kwds["strict"] = True # Useless in Python3, would generate a deprecation warning | |
| 318 if atLeastPython26: # pragma no branch (Branch useful only with Python 2.5) | |
| 319 kwds["timeout"] = self.__timeout # Did not exist before Python2.6 | |
| 320 return self.__connectionClass(self.__hostname, self.__port, **kwds) | |
| 321 | |
| 322 def __log(self, verb, url, requestHeaders, input, status, responseHeaders, output): | |
| 323 logger = logging.getLogger(__name__) | |
| 324 if logger.isEnabledFor(logging.DEBUG): | |
| 325 if "Authorization" in requestHeaders: | |
| 326 if requestHeaders["Authorization"].startswith("Basic"): | |
| 327 requestHeaders["Authorization"] = "Basic (login and password removed)" | |
| 328 elif requestHeaders["Authorization"].startswith("token"): | |
| 329 requestHeaders["Authorization"] = "token (oauth token removed)" | |
| 330 else: # pragma no cover (Cannot happen, but could if we add an authentication method => be prepared) | |
| 331 requestHeaders["Authorization"] = "(unknown auth removed)" # pragma no cover (Cannot happen, but could if we add an authentication method => be prepared) | |
| 332 logger.debug("%s %s://%s%s %s %s ==> %i %s %s", str(verb), self.__scheme, self.__hostname, str(url), str(requestHeaders), str(input), status, str(responseHeaders), str(output)) |
