mirror of
https://github.com/status-im/PyGithub.git
synced 2026-08-31 10:51:14 +00:00
Sync GithubIntegration __init__ arguments with github.Github (#2556)
Github and GithubIntegration now both support the same (full) set of Requester arguments. Creating a Github instance for a Github App Installation coming from GithubIntegration uses the same Requester arguments (except for auth).
This commit is contained in:
@@ -51,6 +51,15 @@ when authenticated as a Github App:
|
||||
... installation.id
|
||||
'1234567'
|
||||
|
||||
Get a ``github.Github`` instance authenticated as an App installation:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> installation = gi.get_installations()[0]
|
||||
>>> g = installation.get_github_for_installation()
|
||||
>>> g.get_repo("user/repo").name
|
||||
'repo'
|
||||
|
||||
App installation authentication
|
||||
-------------------------------
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ repoVisibilityPreview = "application/vnd.github.nebula-preview+json"
|
||||
|
||||
DEFAULT_BASE_URL = "https://api.github.com"
|
||||
DEFAULT_STATUS_URL = "https://status.github.com"
|
||||
DEFAULT_USER_AGENT = "PyGithub/Python"
|
||||
# As of 2018-05-17, Github imposes a 10s limit for completion of API requests.
|
||||
# Thus, the timeout should be slightly > 10s to account for network/front-end
|
||||
# latency.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import warnings
|
||||
|
||||
import deprecated
|
||||
import urllib3
|
||||
|
||||
import github
|
||||
from github import Consts
|
||||
from github.Auth import AppAuth
|
||||
from github.GithubApp import GithubApp
|
||||
@@ -17,6 +19,7 @@ class GithubIntegration:
|
||||
Main class to obtain tokens for a GitHub integration.
|
||||
"""
|
||||
|
||||
# keep non-deprecated arguments in-sync with Requester
|
||||
# v2: remove integration_id, private_key, jwt_expiry, jwt_issued_at and jwt_algorithm
|
||||
# v2: move auth to the front of arguments
|
||||
# v2: add * before first argument so all arguments must be named,
|
||||
@@ -26,6 +29,12 @@ class GithubIntegration:
|
||||
integration_id=None,
|
||||
private_key=None,
|
||||
base_url=Consts.DEFAULT_BASE_URL,
|
||||
timeout=Consts.DEFAULT_TIMEOUT,
|
||||
user_agent=Consts.DEFAULT_USER_AGENT,
|
||||
per_page=Consts.DEFAULT_PER_PAGE,
|
||||
verify=True,
|
||||
retry=None,
|
||||
pool_size=None,
|
||||
jwt_expiry=Consts.DEFAULT_JWT_EXPIRY,
|
||||
jwt_issued_at=Consts.DEFAULT_JWT_ISSUED_AT,
|
||||
jwt_algorithm=Consts.DEFAULT_JWT_ALGORITHM,
|
||||
@@ -35,8 +44,15 @@ class GithubIntegration:
|
||||
:param integration_id: int deprecated, use auth=github.Auth.AppAuth(...) instead
|
||||
:param private_key: string deprecated, use auth=github.Auth.AppAuth(...) instead
|
||||
:param base_url: string
|
||||
:param timeout: integer
|
||||
:param user_agent: string
|
||||
:param per_page: int
|
||||
:param verify: boolean or string
|
||||
:param retry: int or urllib3.util.retry.Retry object
|
||||
:param pool_size: int
|
||||
:param jwt_expiry: int deprecated, use auth=github.Auth.AppAuth(...) instead
|
||||
:param jwt_issued_at: int deprecated, use auth=github.Auth.AppAuth(...) instead
|
||||
:param jwt_algorithm: string deprecated, use auth=github.Auth.AppAuth(...) instead
|
||||
:param auth: authentication method
|
||||
"""
|
||||
if integration_id is not None:
|
||||
@@ -46,6 +62,16 @@ class GithubIntegration:
|
||||
private_key, str
|
||||
), "supplied private key should be a string"
|
||||
assert isinstance(base_url, str), base_url
|
||||
assert isinstance(timeout, int), timeout
|
||||
assert user_agent is None or isinstance(user_agent, str), user_agent
|
||||
assert isinstance(per_page, int), per_page
|
||||
assert isinstance(verify, (bool, str)), verify
|
||||
assert (
|
||||
retry is None
|
||||
or isinstance(retry, int)
|
||||
or isinstance(retry, urllib3.util.Retry)
|
||||
), retry
|
||||
assert pool_size is None or isinstance(pool_size, int), pool_size
|
||||
assert isinstance(jwt_expiry, int), jwt_expiry
|
||||
assert Consts.MIN_JWT_EXPIRY <= jwt_expiry <= Consts.MAX_JWT_EXPIRY, jwt_expiry
|
||||
assert isinstance(jwt_issued_at, int)
|
||||
@@ -81,14 +107,21 @@ class GithubIntegration:
|
||||
self.__requester = Requester(
|
||||
auth=auth,
|
||||
base_url=self.base_url,
|
||||
timeout=Consts.DEFAULT_TIMEOUT,
|
||||
user_agent="PyGithub/Python",
|
||||
per_page=Consts.DEFAULT_PER_PAGE,
|
||||
verify=True,
|
||||
retry=None,
|
||||
pool_size=None,
|
||||
timeout=timeout,
|
||||
user_agent=user_agent,
|
||||
per_page=per_page,
|
||||
verify=verify,
|
||||
retry=retry,
|
||||
pool_size=pool_size,
|
||||
)
|
||||
|
||||
def get_github_for_installation(self, installation_id):
|
||||
# The installation has to authenticate as an installation, not an app
|
||||
auth = self.auth.get_installation_auth(
|
||||
installation_id, requester=self.__requester
|
||||
)
|
||||
return github.Github(**self.__requester.withAuth(auth).kwargs)
|
||||
|
||||
def _get_headers(self):
|
||||
"""
|
||||
Get headers for the requests.
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from typing import Union, Optional, Dict
|
||||
|
||||
from urllib3 import Retry
|
||||
|
||||
import github
|
||||
from github.Auth import AppAuth
|
||||
from github.Installation import Installation
|
||||
from github.InstallationAuthorization import InstallationAuthorization
|
||||
@@ -15,11 +18,18 @@ class GithubIntegration:
|
||||
integration_id: Optional[Union[int, str]] = ...,
|
||||
private_key: Optional[str] = ...,
|
||||
base_url: str = ...,
|
||||
timeout: int = ...,
|
||||
user_agent: str = ...,
|
||||
per_page: int = ...,
|
||||
verify: Union[bool, str] = ...,
|
||||
retry: Optional[Union[int, Retry]] = ...,
|
||||
pool_size: Optional[int] = ...,
|
||||
jwt_expiry: int = ...,
|
||||
jwt_issued_at: int = ...,
|
||||
jwt_algorithm: str = ...,
|
||||
auth: Optional[AppAuth] = ...,
|
||||
) -> None: ...
|
||||
def get_github_for_installation(self, installation_id: int) -> github.Github: ...
|
||||
def _get_installed_app(self, url: str) -> Installation: ...
|
||||
def _get_headers(self) -> Dict[str, str]: ...
|
||||
def create_jwt(self, expiration: Optional[int] = ...) -> str: ...
|
||||
|
||||
@@ -60,6 +60,9 @@ class Installation(github.GithubObject.NonCompletableGithubObject):
|
||||
def __repr__(self):
|
||||
return self.get__repr__({"id": self._id.value})
|
||||
|
||||
def get_github_for_installation(self):
|
||||
return github.Github(**self._requester.kwargs)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
import github
|
||||
from github.GithubObject import NonCompletableGithubObject
|
||||
from github.PaginatedList import PaginatedList
|
||||
from github.Repository import Repository
|
||||
@@ -7,6 +8,7 @@ from github.Repository import Repository
|
||||
class Installation(NonCompletableGithubObject):
|
||||
def _initAttributes(self) -> None: ...
|
||||
def _useAttributes(self, attributes: Dict[str, Any]) -> None: ...
|
||||
def get_github_for_installation(self) -> github.Github: ...
|
||||
@property
|
||||
def id(self) -> int: ...
|
||||
def get_repos(self) -> PaginatedList[Repository]: ...
|
||||
|
||||
+4
-1
@@ -83,6 +83,7 @@ class Github:
|
||||
This is the main class you instantiate to access the Github API v3. Optional parameters allow different authentication methods.
|
||||
"""
|
||||
|
||||
# keep non-deprecated arguments in-sync with Requester
|
||||
# v2: remove login_or_token, password, jwt and app_auth
|
||||
# v2: move auth to the front of arguments
|
||||
# v2: add * before first argument so all arguments must be named,
|
||||
@@ -95,7 +96,7 @@ class Github:
|
||||
app_auth=None,
|
||||
base_url=Consts.DEFAULT_BASE_URL,
|
||||
timeout=Consts.DEFAULT_TIMEOUT,
|
||||
user_agent="PyGithub/Python",
|
||||
user_agent=Consts.DEFAULT_USER_AGENT,
|
||||
per_page=Consts.DEFAULT_PER_PAGE,
|
||||
verify=True,
|
||||
retry=None,
|
||||
@@ -123,6 +124,8 @@ class Github:
|
||||
assert isinstance(base_url, str), base_url
|
||||
assert isinstance(timeout, int), timeout
|
||||
assert user_agent is None or isinstance(user_agent, str), user_agent
|
||||
assert isinstance(per_page, int), per_page
|
||||
assert isinstance(verify, (bool, str)), verify
|
||||
assert (
|
||||
retry is None
|
||||
or isinstance(retry, int)
|
||||
|
||||
@@ -38,7 +38,7 @@ class Github:
|
||||
timeout: int = ...,
|
||||
user_agent: str = ...,
|
||||
per_page: int = ...,
|
||||
verify: bool = ...,
|
||||
verify: Union[bool, str] = ...,
|
||||
retry: Optional[Union[int, Retry]] = ...,
|
||||
pool_size: Optional[int] = ...,
|
||||
auth: Optional[Auth] = ...,
|
||||
@@ -95,6 +95,8 @@ class Github:
|
||||
self, since: Union[int, _NotSetType] = ...
|
||||
) -> PaginatedList[NamedUser]: ...
|
||||
def load(self, f: BytesIO) -> Repository: ...
|
||||
# argument slug is deprecated, not included here
|
||||
def get_app(self): ...
|
||||
def get_oauth_application(
|
||||
self, client_id: str, client_secret: str
|
||||
) -> ApplicationOAuth: ...
|
||||
|
||||
+24
-12
@@ -344,6 +344,7 @@ class Requester:
|
||||
__hostname: str
|
||||
__authorizationHeader: Optional[str]
|
||||
|
||||
# keep arguments in-sync with github.MainClass and GithubIntegration
|
||||
def __init__(
|
||||
self,
|
||||
auth: Optional["Auth"],
|
||||
@@ -351,7 +352,7 @@ class Requester:
|
||||
timeout: int,
|
||||
user_agent: str,
|
||||
per_page: int,
|
||||
verify: bool,
|
||||
verify: Union[bool, str],
|
||||
retry: Optional[Union[int, Retry]],
|
||||
pool_size: Optional[int],
|
||||
):
|
||||
@@ -394,6 +395,24 @@ class Requester:
|
||||
if isinstance(self.__auth, WithRequester):
|
||||
self.__auth.withRequester(self)
|
||||
|
||||
@property
|
||||
def kwargs(self):
|
||||
"""
|
||||
Returns arguments required to recreate this Requester with Requester.__init__, as well as
|
||||
with MainClass.__init__ and GithubIntegration.__init__.
|
||||
:return:
|
||||
"""
|
||||
return dict(
|
||||
auth=self.__auth,
|
||||
base_url=self.__base_url,
|
||||
timeout=self.__timeout,
|
||||
user_agent=self.__userAgent,
|
||||
per_page=self.per_page,
|
||||
verify=self.__verify,
|
||||
retry=self.__retry,
|
||||
pool_size=self.__pool_size,
|
||||
)
|
||||
|
||||
@property
|
||||
def base_url(self) -> str:
|
||||
return self.__base_url
|
||||
@@ -406,18 +425,11 @@ class Requester:
|
||||
"""
|
||||
Create a new requester instance with identical configuration but the given authentication method.
|
||||
:param auth: authentication method
|
||||
:return: new Reqester implementation
|
||||
:return: new Requester implementation
|
||||
"""
|
||||
return Requester(
|
||||
auth=auth,
|
||||
base_url=self.__base_url,
|
||||
timeout=self.__timeout,
|
||||
user_agent=self.__userAgent,
|
||||
per_page=self.per_page,
|
||||
verify=self.__verify,
|
||||
retry=self.__retry,
|
||||
pool_size=self.__pool_size,
|
||||
)
|
||||
kwargs = self.kwargs
|
||||
kwargs.update(auth=auth)
|
||||
return Requester(**kwargs)
|
||||
|
||||
def requestJsonAndCheck(
|
||||
self,
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
@@ -344,6 +345,12 @@ class BasicTestCase(unittest.TestCase):
|
||||
]
|
||||
self.assertSequenceEqual(actual, expected)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ignoreWarning(self, category=Warning, module=""):
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore", category=category, module=module)
|
||||
yield
|
||||
|
||||
def __openFile(self, mode):
|
||||
for (_, _, functionName, _) in traceback.extract_stack():
|
||||
if (
|
||||
|
||||
+3
-6
@@ -106,12 +106,9 @@ class GithubApp(Framework.TestCase):
|
||||
g = github.Github(auth=auth)
|
||||
|
||||
with self.assertWarns(DeprecationWarning) as warning:
|
||||
# we ignore warnings from httpretty dependency
|
||||
import warnings
|
||||
|
||||
warnings.filterwarnings("ignore", module="httpretty")
|
||||
|
||||
app = g.get_app()
|
||||
# httpretty has some deprecation warnings in Python 3.12
|
||||
with self.ignoreWarning(category=DeprecationWarning, module="httpretty"):
|
||||
app = g.get_app()
|
||||
|
||||
self.assertWarning(
|
||||
warning,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import time # NOQA
|
||||
|
||||
import requests # NOQA
|
||||
from urllib3.exceptions import InsecureRequestWarning
|
||||
|
||||
import github
|
||||
from github import Consts
|
||||
from github.Auth import AppInstallationAuth
|
||||
|
||||
from . import Framework
|
||||
|
||||
@@ -86,6 +89,46 @@ class GithubIntegration(Framework.BasicTestCase):
|
||||
self.assertEqual(installations[0].id, self.org_installation_id)
|
||||
self.assertEqual(installations[1].id, self.repo_installation_id)
|
||||
|
||||
def testGetGithubForInstallation(self):
|
||||
# with verify=False, urllib3.connectionpool rightly may issue an InsecureRequestWarning
|
||||
# we ignore InsecureRequestWarning from urllib3.connectionpool
|
||||
with self.ignoreWarning(
|
||||
category=InsecureRequestWarning, module="urllib3.connectionpool"
|
||||
):
|
||||
auth = github.Auth.AppAuth(APP_ID, PRIVATE_KEY)
|
||||
github_integration = github.GithubIntegration(
|
||||
auth=auth,
|
||||
base_url="https://api.github.com",
|
||||
timeout=Consts.DEFAULT_TIMEOUT + 10,
|
||||
user_agent="PyGithub/Python-Test",
|
||||
per_page=Consts.DEFAULT_PER_PAGE + 10,
|
||||
verify=False,
|
||||
retry=3,
|
||||
pool_size=10,
|
||||
)
|
||||
|
||||
g = github_integration.get_github_for_installation(36541767)
|
||||
|
||||
self.assertIsInstance(g._Github__requester.auth, AppInstallationAuth)
|
||||
self.assertEqual(
|
||||
g._Github__requester._Requester__base_url, "https://api.github.com"
|
||||
)
|
||||
self.assertEqual(
|
||||
g._Github__requester._Requester__timeout, Consts.DEFAULT_TIMEOUT + 10
|
||||
)
|
||||
self.assertEqual(
|
||||
g._Github__requester._Requester__userAgent, "PyGithub/Python-Test"
|
||||
)
|
||||
self.assertEqual(
|
||||
g._Github__requester.per_page, Consts.DEFAULT_PER_PAGE + 10
|
||||
)
|
||||
self.assertEqual(g._Github__requester._Requester__verify, False)
|
||||
self.assertEqual(g._Github__requester._Requester__retry, 3)
|
||||
self.assertEqual(g._Github__requester._Requester__pool_size, 10)
|
||||
|
||||
repo = g.get_repo("PyGithub/PyGithub")
|
||||
self.assertEqual(repo.full_name, "PyGithub/PyGithub")
|
||||
|
||||
def testGetAccessToken(self):
|
||||
auth = github.Auth.AppAuth(APP_ID, PRIVATE_KEY)
|
||||
github_integration = github.GithubIntegration(auth=auth)
|
||||
|
||||
+46
-1
@@ -20,8 +20,11 @@
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
from urllib3.exceptions import InsecureRequestWarning
|
||||
|
||||
import github
|
||||
from github.Auth import AppAuth
|
||||
from github import Consts
|
||||
from github.Auth import AppAuth, AppInstallationAuth
|
||||
|
||||
from . import Framework, GithubIntegration
|
||||
|
||||
@@ -44,3 +47,45 @@ class Installation(Framework.BasicTestCase):
|
||||
self.assertListEqual(
|
||||
[repo.full_name for repo in repos], ["EnricoMi/sandbox", "EnricoMi/python"]
|
||||
)
|
||||
|
||||
def testGetGithubForInstallation(self):
|
||||
# with verify=False, urllib3.connectionpool rightly may issue an InsecureRequestWarning
|
||||
# we ignore InsecureRequestWarning from urllib3.connectionpool
|
||||
with self.ignoreWarning(
|
||||
category=InsecureRequestWarning, module="urllib3.connectionpool"
|
||||
):
|
||||
self.auth = AppAuth(319953, GithubIntegration.PRIVATE_KEY)
|
||||
self.integration = github.GithubIntegration(
|
||||
auth=self.auth,
|
||||
base_url="https://api.github.com",
|
||||
timeout=Consts.DEFAULT_TIMEOUT + 10,
|
||||
user_agent="PyGithub/Python-Test",
|
||||
per_page=Consts.DEFAULT_PER_PAGE + 10,
|
||||
verify=False,
|
||||
retry=3,
|
||||
pool_size=10,
|
||||
)
|
||||
installations = list(self.integration.get_installations())
|
||||
installation = installations[0]
|
||||
|
||||
g = installation.get_github_for_installation()
|
||||
|
||||
self.assertIsInstance(g._Github__requester.auth, AppInstallationAuth)
|
||||
self.assertEqual(
|
||||
g._Github__requester._Requester__base_url, "https://api.github.com"
|
||||
)
|
||||
self.assertEqual(
|
||||
g._Github__requester._Requester__timeout, Consts.DEFAULT_TIMEOUT + 10
|
||||
)
|
||||
self.assertEqual(
|
||||
g._Github__requester._Requester__userAgent, "PyGithub/Python-Test"
|
||||
)
|
||||
self.assertEqual(
|
||||
g._Github__requester.per_page, Consts.DEFAULT_PER_PAGE + 10
|
||||
)
|
||||
self.assertEqual(g._Github__requester._Requester__verify, False)
|
||||
self.assertEqual(g._Github__requester._Requester__retry, 3)
|
||||
self.assertEqual(g._Github__requester._Requester__pool_size, 10)
|
||||
|
||||
repo = g.get_repo("PyGithub/PyGithub")
|
||||
self.assertEqual(repo.full_name, "PyGithub/PyGithub")
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -39,6 +39,90 @@ class Requester(Framework.TestCase):
|
||||
github.Requester.Requester.resetLogger()
|
||||
super().tearDown()
|
||||
|
||||
def testRecreation(self):
|
||||
class TestAuth(github.Auth.AppAuth):
|
||||
pass
|
||||
|
||||
# create a Requester with non-default arguments
|
||||
auth = TestAuth(123, "key")
|
||||
requester = github.Requester.Requester(
|
||||
auth=auth,
|
||||
base_url="https://base.url",
|
||||
timeout=1,
|
||||
user_agent="user agent",
|
||||
per_page=123,
|
||||
verify=False,
|
||||
retry=3,
|
||||
pool_size=5,
|
||||
)
|
||||
kwargs = requester.kwargs
|
||||
|
||||
# assert kwargs consists of ALL constructor arguments
|
||||
self.assertEqual(
|
||||
kwargs.keys(), github.Requester.Requester.__init__.__annotations__.keys()
|
||||
)
|
||||
self.assertEqual(
|
||||
kwargs,
|
||||
dict(
|
||||
auth=auth,
|
||||
base_url="https://base.url",
|
||||
timeout=1,
|
||||
user_agent="user agent",
|
||||
per_page=123,
|
||||
verify=False,
|
||||
retry=3,
|
||||
pool_size=5,
|
||||
),
|
||||
)
|
||||
|
||||
# create a copy Requester, assert identity via kwargs
|
||||
copy = github.Requester.Requester(**kwargs)
|
||||
self.assertEqual(copy.kwargs, kwargs)
|
||||
|
||||
# create Github instance, assert identity requester
|
||||
gh = github.Github(**kwargs)
|
||||
self.assertEqual(gh._Github__requester.kwargs, kwargs)
|
||||
|
||||
# create GithubIntegration instance, assert identity requester
|
||||
gi = github.GithubIntegration(**kwargs)
|
||||
self.assertEqual(gi._GithubIntegration__requester.kwargs, kwargs)
|
||||
|
||||
def testWithAuth(self):
|
||||
class TestAuth(github.Auth.AppAuth):
|
||||
pass
|
||||
|
||||
# create a Requester with non-default arguments
|
||||
auth = TestAuth(123, "key")
|
||||
requester = github.Requester.Requester(
|
||||
auth=auth,
|
||||
base_url="https://base.url",
|
||||
timeout=1,
|
||||
user_agent="user agent",
|
||||
per_page=123,
|
||||
verify=False,
|
||||
retry=3,
|
||||
pool_size=5,
|
||||
)
|
||||
|
||||
# create a copy with different auth
|
||||
auth2 = TestAuth(456, "key2")
|
||||
copy = requester.withAuth(auth2)
|
||||
|
||||
# assert kwargs of copy
|
||||
self.assertEqual(
|
||||
copy.kwargs,
|
||||
dict(
|
||||
auth=auth2,
|
||||
base_url="https://base.url",
|
||||
timeout=1,
|
||||
user_agent="user agent",
|
||||
per_page=123,
|
||||
verify=False,
|
||||
retry=3,
|
||||
pool_size=5,
|
||||
),
|
||||
)
|
||||
|
||||
def testLoggingRedirection(self):
|
||||
self.assertEqual(self.g.get_repo("EnricoMi/test").name, "test-renamed")
|
||||
self.logger.info.assert_called_once_with(
|
||||
|
||||
Reference in New Issue
Block a user