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))