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:
Isac Souza
2019-04-05 13:49:01 +08:00
committed by Wan Liuyang
parent 6efd631890
commit 5ae7af55ea
13 changed files with 311 additions and 28 deletions
+6 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -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
View File
@@ -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"}
+86
View File
@@ -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')
+2 -1
View File
@@ -113,6 +113,7 @@ if __name__ == "__main__":
"integrations": ["cryptography"]
},
tests_require=[
"cryptography"
"cryptography",
"httpretty==0.9.6"
]
)
+1
View File
@@ -0,0 +1 @@
httpretty==0.9.6