mirror of
https://github.com/status-im/PyGithub.git
synced 2026-08-31 10:51:14 +00:00
Adding communications Retry functionality into requests via urllib3 retry object. (#1002)
This is a continuation of the work started by @allevin on https://github.com/PyGithub/PyGithub/pull/860. I have refactored the testing Framework to use the `httpretty` library in order to use the urllib3 retry feature and therefore be able to test it. Please refer to https://github.com/PyGithub/PyGithub/pull/860 for additional context. cc: @allevin @mfonville @jrouquie @sfdye Closes #757 Closes #860
This commit is contained in:
+6
-1
@@ -53,7 +53,12 @@ def get_protected_branch(self):
|
||||
|
||||
## Automated tests
|
||||
|
||||
You can run the tests through `python -m github.tests`.
|
||||
First you need to install the test dependencies:
|
||||
```bash
|
||||
pip install -r test-requirements.txt
|
||||
```
|
||||
|
||||
Then you can run the tests through `python -m github.tests`.
|
||||
Run a specific test with `python -m github.tests TestCase` or `python -m github.tests TestCase.testMethod`.
|
||||
|
||||
If you add a new test, for example `Issue139.testCompletion`, you must add an import in `github/tests/AllTests.py`.
|
||||
|
||||
+5
-2
@@ -55,6 +55,7 @@ import time
|
||||
import sys
|
||||
import requests
|
||||
import jwt
|
||||
import urllib3
|
||||
|
||||
from Requester import Requester
|
||||
import AuthenticatedUser
|
||||
@@ -93,7 +94,7 @@ class Github(object):
|
||||
This is the main class you instantiate to access the Github API v3. Optional parameters allow different authentication methods.
|
||||
"""
|
||||
|
||||
def __init__(self, login_or_token=None, password=None, jwt=None, base_url=DEFAULT_BASE_URL, timeout=DEFAULT_TIMEOUT, client_id=None, client_secret=None, user_agent='PyGithub/Python', per_page=DEFAULT_PER_PAGE, api_preview=False, verify=True):
|
||||
def __init__(self, login_or_token=None, password=None, jwt=None, base_url=DEFAULT_BASE_URL, timeout=DEFAULT_TIMEOUT, client_id=None, client_secret=None, user_agent='PyGithub/Python', per_page=DEFAULT_PER_PAGE, api_preview=False, verify=True, retry=None):
|
||||
"""
|
||||
:param login_or_token: string
|
||||
:param password: string
|
||||
@@ -104,6 +105,7 @@ class Github(object):
|
||||
:param user_agent: string
|
||||
:param per_page: int
|
||||
:param verify: boolean or string
|
||||
:param retry: int or urllib3.util.retry.Retry object
|
||||
"""
|
||||
|
||||
assert login_or_token is None or isinstance(login_or_token, (str, unicode)), login_or_token
|
||||
@@ -115,7 +117,8 @@ class Github(object):
|
||||
assert client_secret is None or isinstance(client_secret, (str, unicode)), client_secret
|
||||
assert user_agent is None or isinstance(user_agent, (str, unicode)), user_agent
|
||||
assert isinstance(api_preview, (bool))
|
||||
self.__requester = Requester(login_or_token, password, jwt, base_url, timeout, client_id, client_secret, user_agent, per_page, api_preview, verify)
|
||||
assert retry is None or isinstance(retry, (int)) or isinstance(retry, (urllib3.util.Retry))
|
||||
self.__requester = Requester(login_or_token, password, jwt, base_url, timeout, client_id, client_secret, user_agent, per_page, api_preview, verify, retry)
|
||||
|
||||
def __get_FIX_REPO_GET_GIT_REF(self):
|
||||
"""
|
||||
|
||||
+18
-7
@@ -89,13 +89,18 @@ class RequestsResponse:
|
||||
|
||||
class HTTPSRequestsConnectionClass(object):
|
||||
# mimic the httplib connection object
|
||||
def __init__(self, host, port=None, strict=False, timeout=None, **kwargs):
|
||||
def __init__(self, host, port=None, strict=False, timeout=None, retry=None, **kwargs):
|
||||
self.port = port if port else 443
|
||||
self.host = host
|
||||
self.protocol = "https"
|
||||
self.timeout = timeout
|
||||
self.verify = kwargs.get("verify", True)
|
||||
self.session = requests.Session()
|
||||
# Code to support retries
|
||||
if retry:
|
||||
self.retry = retry
|
||||
self.adapter = requests.adapters.HTTPAdapter(max_retries=self.retry)
|
||||
self.session.mount('https://', self.adapter)
|
||||
|
||||
def request(self, verb, url, input, headers):
|
||||
self.verb = verb
|
||||
@@ -115,13 +120,18 @@ class HTTPSRequestsConnectionClass(object):
|
||||
|
||||
class HTTPRequestsConnectionClass(object):
|
||||
# mimic the httplib connection object
|
||||
def __init__(self, host, port=None, strict=False, timeout=None, **kwargs):
|
||||
def __init__(self, host, port=None, strict=False, timeout=None, retry=None, **kwargs):
|
||||
self.port = port if port else 80
|
||||
self.host = host
|
||||
self.protocol = "http"
|
||||
self.timeout = timeout
|
||||
self.verify = kwargs.get("verify", True)
|
||||
self.session = requests.Session()
|
||||
# Code to support retries
|
||||
if retry:
|
||||
self.retry = retry
|
||||
self.adapter = requests.adapters.HTTPAdapter(max_retries=self.retry)
|
||||
self.session.mount('http://', self.adapter)
|
||||
|
||||
def request(self, verb, url, input, headers):
|
||||
self.verb = verb
|
||||
@@ -214,7 +224,7 @@ class Requester:
|
||||
|
||||
#############################################################
|
||||
|
||||
def __init__(self, login_or_token, password, jwt, base_url, timeout, client_id, client_secret, user_agent, per_page, api_preview, verify):
|
||||
def __init__(self, login_or_token, password, jwt, base_url, timeout, client_id, client_secret, user_agent, per_page, api_preview, verify, retry):
|
||||
self._initializeDebugFeature()
|
||||
|
||||
if password is not None:
|
||||
@@ -237,6 +247,7 @@ class Requester:
|
||||
self.__port = o.port
|
||||
self.__prefix = o.path
|
||||
self.__timeout = timeout
|
||||
self.__retry = retry # NOTE: retry can be either int or an urllib3 Retry object
|
||||
self.__scheme = o.scheme
|
||||
if o.scheme == "https":
|
||||
self.__connectionClass = self.__httpsConnectionClass
|
||||
@@ -283,9 +294,9 @@ class Requester:
|
||||
(o.port and o.port != self.__port) or \
|
||||
(o.scheme != self.__scheme and not (o.scheme == "https" and self.__scheme == "http")): # issue80
|
||||
if o.scheme == 'http':
|
||||
cnx = self.__httpConnectionClass(o.hostname, o.port)
|
||||
cnx = self.__httpConnectionClass(o.hostname, o.port, retry = self.__retry)
|
||||
elif o.scheme == 'https':
|
||||
cnx = self.__httpsConnectionClass(o.hostname, o.port)
|
||||
cnx = self.__httpsConnectionClass(o.hostname, o.port, retry = self.__retry)
|
||||
return cnx
|
||||
|
||||
def __createException(self, status, headers, output):
|
||||
@@ -459,7 +470,7 @@ class Requester:
|
||||
if self.__persist and self.__connection is not None:
|
||||
return self.__connection
|
||||
|
||||
self.__connection = self.__connectionClass(self.__hostname, self.__port, **kwds)
|
||||
self.__connection = self.__connectionClass(self.__hostname, self.__port, retry = self.__retry, **kwds)
|
||||
|
||||
return self.__connection
|
||||
|
||||
@@ -475,4 +486,4 @@ class Requester:
|
||||
requestHeaders["Authorization"] = "Bearer (jwt removed)"
|
||||
else: # pragma no cover (Cannot happen, but could if we add an authentication method => be prepared)
|
||||
requestHeaders["Authorization"] = "(unknown auth removed)" # pragma no cover (Cannot happen, but could if we add an authentication method => be prepared)
|
||||
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))
|
||||
logger.debug("%s %s://%s%s %s %s ==> %i %s %s", verb, self.__scheme, self.__hostname, url, requestHeaders, input, status, responseHeaders, output)
|
||||
|
||||
@@ -104,6 +104,7 @@ from ExposeAllAttributes import *
|
||||
from BadAttributes import *
|
||||
from Equality import *
|
||||
from Search import *
|
||||
from Retry import *
|
||||
|
||||
from Issue33 import *
|
||||
from Issue50 import *
|
||||
|
||||
+69
-15
@@ -41,6 +41,9 @@ import os
|
||||
import sys
|
||||
import traceback
|
||||
import unittest
|
||||
import httpretty
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
from urllib3.util import Url
|
||||
|
||||
import github
|
||||
|
||||
@@ -163,14 +166,28 @@ class ReplayingConnection:
|
||||
self.__file = file
|
||||
self.__protocol = protocol
|
||||
self.__host = host
|
||||
self.__port = str(port)
|
||||
self.__port = port
|
||||
self.response_headers = CaseInsensitiveDict()
|
||||
|
||||
self.__cnx = self._realConnection(host, port, *args, **kwds)
|
||||
|
||||
def request(self, verb, url, input, headers):
|
||||
full_url = Url(scheme=self.__protocol, host=self.__host, port=self.__port, path=url)
|
||||
|
||||
httpretty.register_uri(
|
||||
verb,
|
||||
full_url.url,
|
||||
body=self.__request_callback
|
||||
)
|
||||
|
||||
self.__cnx.request(verb, url, input, headers)
|
||||
|
||||
def __readNextRequest(self, verb, url, input, headers):
|
||||
fixAuthorizationHeader(headers)
|
||||
self.__testCase.assertEqual(self.__protocol, readLine(self.__file))
|
||||
self.__testCase.assertEqual(verb, readLine(self.__file))
|
||||
self.__testCase.assertEqual(self.__host, readLine(self.__file))
|
||||
self.__testCase.assertEqual(self.__port, readLine(self.__file))
|
||||
self.__testCase.assertEqual(str(self.__port), readLine(self.__file))
|
||||
self.__testCase.assertEqual(self.__splitUrl(url), self.__splitUrl(readLine(self.__file)))
|
||||
self.__testCase.assertEqual(headers, eval(readLine(self.__file)))
|
||||
expectedInput = readLine(self.__file)
|
||||
@@ -193,29 +210,58 @@ class ReplayingConnection:
|
||||
base, qs = splitedUrl
|
||||
return (base, sorted(qs.split("&")))
|
||||
|
||||
def getresponse(self):
|
||||
def __request_callback(self, request, uri, response_headers):
|
||||
self.__readNextRequest(self.__cnx.verb, self.__cnx.url, self.__cnx.input, self.__cnx.headers)
|
||||
|
||||
status = int(readLine(self.__file))
|
||||
headers = eval(readLine(self.__file))
|
||||
self.response_headers = CaseInsensitiveDict(eval(readLine(self.__file)))
|
||||
output = readLine(self.__file)
|
||||
|
||||
return FakeHttpResponse(status, headers, output)
|
||||
|
||||
def close(self):
|
||||
readLine(self.__file)
|
||||
|
||||
if atLeastPython3:
|
||||
output = bytes(output, 'utf-8')
|
||||
|
||||
def ReplayingHttpConnection(testCase, file, *args, **kwds):
|
||||
return ReplayingConnection(testCase, file, "http", *args, **kwds)
|
||||
# make a copy of the headers and remove the ones that interfere with the response handling
|
||||
adding_headers = CaseInsensitiveDict(self.response_headers)
|
||||
adding_headers.pop('content-length', None)
|
||||
adding_headers.pop('transfer-encoding', None)
|
||||
adding_headers.pop('content-encoding', None)
|
||||
|
||||
response_headers.update(adding_headers)
|
||||
return [status, response_headers, output]
|
||||
|
||||
def getresponse(self):
|
||||
# call original connection, this will go all the way down to the python socket and will be intercepted by httpretty
|
||||
response = self.__cnx.getresponse()
|
||||
|
||||
# restore original headers to the response
|
||||
response.headers = self.response_headers
|
||||
|
||||
return response
|
||||
|
||||
def close(self):
|
||||
self.__cnx.close()
|
||||
|
||||
|
||||
def ReplayingHttpsConnection(testCase, file, *args, **kwds):
|
||||
return ReplayingConnection(testCase, file, "https", *args, **kwds)
|
||||
class ReplayingHttpConnection(ReplayingConnection):
|
||||
_realConnection = github.Requester.HTTPRequestsConnectionClass
|
||||
|
||||
def __init__(self, testCase, file, *args, **kwds):
|
||||
ReplayingConnection.__init__(self, testCase, file, "http", *args, **kwds)
|
||||
|
||||
|
||||
class ReplayingHttpsConnection(ReplayingConnection):
|
||||
_realConnection = github.Requester.HTTPSRequestsConnectionClass
|
||||
|
||||
def __init__(self, testCase, file, *args, **kwds):
|
||||
ReplayingConnection.__init__(self, testCase, file, "https", *args, **kwds)
|
||||
|
||||
|
||||
class BasicTestCase(unittest.TestCase):
|
||||
recordMode = False
|
||||
tokenAuthMode = False
|
||||
jwtAuthMode = False
|
||||
retry = None
|
||||
replayDataFolder = os.path.join(os.path.dirname(__file__), "ReplayData")
|
||||
|
||||
def setUp(self):
|
||||
@@ -247,8 +293,12 @@ class BasicTestCase(unittest.TestCase):
|
||||
self.client_secret = "client_secret"
|
||||
self.jwt = "jwt"
|
||||
|
||||
httpretty.enable(allow_net_connect=False)
|
||||
|
||||
def tearDown(self):
|
||||
unittest.TestCase.tearDown(self)
|
||||
httpretty.disable()
|
||||
httpretty.reset()
|
||||
self.__closeReplayFileIfNeeded()
|
||||
github.Requester.Requester.resetConnectionClasses()
|
||||
|
||||
@@ -298,11 +348,11 @@ class TestCase(BasicTestCase):
|
||||
github.Requester.Requester.setOnCheckMe(self.getFrameChecker())
|
||||
|
||||
if self.tokenAuthMode:
|
||||
self.g = github.Github(self.oauth_token)
|
||||
self.g = github.Github(self.oauth_token, retry=self.retry)
|
||||
elif self.jwtAuthMode:
|
||||
self.g = github.Github(jwt=self.jwt)
|
||||
self.g = github.Github(jwt=self.jwt, retry=self.retry)
|
||||
else:
|
||||
self.g = github.Github(self.login, self.password)
|
||||
self.g = github.Github(self.login, self.password, retry=self.retry)
|
||||
|
||||
|
||||
def activateRecordMode(): # pragma no cover (Function useful only when recording new tests, not used during automated tests)
|
||||
@@ -315,3 +365,7 @@ def activateTokenAuthMode(): # pragma no cover (Function useful only when recor
|
||||
|
||||
def activateJWTAuthMode(): # pragma no cover (Function useful only when recording new tests, not used during automated tests)
|
||||
BasicTestCase.jwtAuthMode = True
|
||||
|
||||
|
||||
def enableRetry(retry):
|
||||
BasicTestCase.retry = retry
|
||||
|
||||
@@ -38,6 +38,6 @@ None
|
||||
/repos/edhollandAL/PyGithub/releases/1210837/assets?label=unit+test+artifact&name=archive.zip
|
||||
{'Authorization': 'Basic login_and_password_removed', 'Content-Length': '140', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/zip'}
|
||||
None
|
||||
204
|
||||
[('content-length', '150'), ('vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding'), ('x-served-by', '139317cebd6caf9cd03889139437f00b'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('etag', '"26708914904a29b8c9fd2ac006beb9db"'), ('access-control-allow-credentials', 'true'), ('status', '200 OK'), ('x-ratelimit-remaining', '4942'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('x-github-request-id', '56BCFFD3:5677:5265FA1:553A03F5'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('last-modified', 'Fri, 24 Apr 2015 08:43:32 GMT'), ('date', 'Fri, 24 Apr 2015 08:51:01 GMT'), ('access-control-allow-origin', '*'), ('content-security-policy', "default-src 'none'"), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-frame-options', 'deny'), ('content-type', 'application/zip'), ('x-ratelimit-reset', '1429867683')]
|
||||
201
|
||||
[('content-length', '155'), ('vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding'), ('x-served-by', '139317cebd6caf9cd03889139437f00b'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('etag', '"26708914904a29b8c9fd2ac006beb9db"'), ('access-control-allow-credentials', 'true'), ('status', '201 CREATED'), ('x-ratelimit-remaining', '4942'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('x-github-request-id', '56BCFFD3:5677:5265FA1:553A03F5'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('last-modified', 'Fri, 24 Apr 2015 08:43:32 GMT'), ('date', 'Fri, 24 Apr 2015 08:51:01 GMT'), ('access-control-allow-origin', '*'), ('content-security-policy', "default-src 'none'"), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-frame-options', 'deny'), ('content-type', 'application/zip'), ('x-ratelimit-reset', '1429867683')]
|
||||
{"url":"/repos/edhollandAL/PyGithub/releases/1210837/assets/xxx","id":"xxx","name":"archive.zip","label":"unit test artifact","uploader":"unittests"}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/repos/PyGithub/PyGithub
|
||||
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
|
||||
None
|
||||
502
|
||||
[('Server', 'GitHub.com'), ('Date', 'Fri, 26 Oct 2018 06:02:42 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Status', '502 Bad Gateway'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4970'), ('X-RateLimit-Reset', '1540536267'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP'), ('ETag', 'W/"3ffd69d4f4e7383eae948812b1eb72e5"'), ('X-OAuth-Scopes', 'repo'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; param=star; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', 'F9D6:235E:3C00DD:772BC6:5BD2AE02')]
|
||||
{"error": "Server Error"}
|
||||
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/repos/PyGithub/PyGithub
|
||||
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
|
||||
None
|
||||
502
|
||||
[('Server', 'GitHub.com'), ('Date', 'Fri, 26 Oct 2018 06:02:42 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Status', '502 Bad Gateway'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4970'), ('X-RateLimit-Reset', '1540536267'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP'), ('ETag', 'W/"3ffd69d4f4e7383eae948812b1eb72e5"'), ('X-OAuth-Scopes', 'repo'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; param=star; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', 'F9D6:235E:3C00DD:772BC6:5BD2AE02')]
|
||||
{"error": "Server Error"}
|
||||
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/repos/PyGithub/PyGithub
|
||||
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
|
||||
None
|
||||
502
|
||||
[('Server', 'GitHub.com'), ('Date', 'Fri, 26 Oct 2018 06:02:42 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Status', '502 Bad Gateway'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4970'), ('X-RateLimit-Reset', '1540536267'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP'), ('ETag', 'W/"3ffd69d4f4e7383eae948812b1eb72e5"'), ('X-OAuth-Scopes', 'repo'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; param=star; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', 'F9D6:235E:3C00DD:772BC6:5BD2AE02')]
|
||||
{"error": "Server Error"}
|
||||
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/repos/PyGithub/PyGithub
|
||||
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
|
||||
None
|
||||
502
|
||||
[('Server', 'GitHub.com'), ('Date', 'Fri, 26 Oct 2018 06:02:42 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Status', '502 Bad Gateway'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4970'), ('X-RateLimit-Reset', '1540536267'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP'), ('ETag', 'W/"3ffd69d4f4e7383eae948812b1eb72e5"'), ('X-OAuth-Scopes', 'repo'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; param=star; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', 'F9D6:235E:3C00DD:772BC6:5BD2AE02')]
|
||||
{"error": "Server Error"}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/repos/PyGithub/PyGithub
|
||||
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
|
||||
None
|
||||
400
|
||||
[('Server', 'GitHub.com'), ('Date', 'Fri, 26 Oct 2018 06:02:42 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Status', '400 Bad Request'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4970'), ('X-RateLimit-Reset', '1540536267'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP'), ('ETag', 'W/"3ffd69d4f4e7383eae948812b1eb72e5"'), ('X-OAuth-Scopes', 'repo'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; param=star; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', 'F9D6:235E:3C00DD:772BC6:5BD2AE02')]
|
||||
{"error": "Bad Request"}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
############################ Copyrights and license ############################
|
||||
# #
|
||||
# Copyright 2018 Justin Kufro <jkufro@andrew.cmu.edu> #
|
||||
# Copyright 2018 Ivan Minno <iminno@andrew.cmu.edu> #
|
||||
# Copyright 2018 Zilei Gu <zileig@andrew.cmu.edu> #
|
||||
# Copyright 2018 Yves Zumbach <yzumbach@andrew.cmu.edu> #
|
||||
# Copyright 2018 Leying Chen <leyingc@andrew.cmu.edu> #
|
||||
# #
|
||||
# This file is part of PyGithub. #
|
||||
# http://pygithub.readthedocs.io/ #
|
||||
# #
|
||||
# PyGithub is free software: you can redistribute it and/or modify it under #
|
||||
# the terms of the GNU Lesser General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY #
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more #
|
||||
# details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU Lesser General Public License #
|
||||
# along with PyGithub. If not, see <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
################################################################################
|
||||
import urllib3
|
||||
from httpretty import httpretty
|
||||
|
||||
import Framework
|
||||
|
||||
import requests
|
||||
|
||||
from github import GithubException
|
||||
from github.Repository import Repository
|
||||
|
||||
REPO_NAME = 'PyGithub/PyGithub'
|
||||
|
||||
|
||||
class Retry(Framework.TestCase):
|
||||
def setUp(self):
|
||||
# status codes returned on random github server errors
|
||||
status_forcelist = (500, 502, 504)
|
||||
retry = urllib3.Retry(
|
||||
total=3,
|
||||
read=3,
|
||||
connect=3,
|
||||
status_forcelist=status_forcelist
|
||||
)
|
||||
|
||||
Framework.enableRetry(retry)
|
||||
Framework.TestCase.setUp(self)
|
||||
|
||||
def testShouldNotRetryWhenStatusNotOnList(self):
|
||||
try:
|
||||
self.g.get_repo(REPO_NAME)
|
||||
except GithubException:
|
||||
self.assertEquals(len(httpretty.latest_requests), 1)
|
||||
|
||||
def testReturnsRepoAfter3Retries(self):
|
||||
repository = self.g.get_repo(REPO_NAME)
|
||||
self.assertEquals(len(httpretty.latest_requests), 4)
|
||||
for request in httpretty.latest_requests:
|
||||
self.assertEquals(request.path, '/repos/' + REPO_NAME)
|
||||
|
||||
self.assertIsInstance(repository, Repository)
|
||||
self.assertEquals(repository.full_name, REPO_NAME)
|
||||
|
||||
def testReturnsRepoAfter1Retry(self):
|
||||
repository = self.g.get_repo(REPO_NAME)
|
||||
self.assertEquals(len(httpretty.latest_requests), 2)
|
||||
for request in httpretty.latest_requests:
|
||||
self.assertEquals(request.path, '/repos/' + REPO_NAME)
|
||||
|
||||
self.assertIsInstance(repository, Repository)
|
||||
self.assertEquals(repository.full_name, REPO_NAME)
|
||||
|
||||
def testRaisesRetryErrorAfterMaxRetries(self):
|
||||
try:
|
||||
response = self.g.get_repo('PyGithub/PyGithub')
|
||||
self.fail("RetryError should have been raised")
|
||||
except requests.exceptions.RetryError:
|
||||
self.assertEquals(len(httpretty.latest_requests), 4)
|
||||
for request in httpretty.latest_requests:
|
||||
self.assertEquals(request.path, '/repos/PyGithub/PyGithub')
|
||||
@@ -113,6 +113,7 @@ if __name__ == "__main__":
|
||||
"integrations": ["cryptography"]
|
||||
},
|
||||
tests_require=[
|
||||
"cryptography"
|
||||
"cryptography",
|
||||
"httpretty==0.9.6"
|
||||
]
|
||||
)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
httpretty==0.9.6
|
||||
Reference in New Issue
Block a user