Skip to content

Commit 8c22fdd

Browse files
kaosx5scoreycaverlyzejunintuitzliu3
authored
feat: sync feature set pygithub (#60)
* fix: drop support for outdated python * fix: update pygithub to support 2.2.0 or higher * fix: update tests for newer pytest * fix: check pygithub 2.4.0 support * fix: check pygithub 2.6.1 support * fix: set lazy to be false by default for backwards compatibility * fix: remove completed param for pygithub 2.6.0 support * fix: add required requester for pygithub 2.6.0 support * fix: fix param typo * fix: make requester a magicmock * fix: update tests for new lazy param * fix: update lazy from str to bool * feat: add support for pool_size and passing down lazy correctly * fix: correct issue with pr creation and pygithub drift * fix: update for issue #59 * revving for release (#57) Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * Updating workflow for python publish pipeline (#58) * Allow path define on get files (#42) * Add support for setting a path on get_files Signed-off-by: kaosx5s <jeremy.xfives@gmail.com> * fix repo test after target change in 3.6.0 Signed-off-by: kaosx5s <> --------- Signed-off-by: kaosx5s <jeremy.xfives@gmail.com> Signed-off-by: kaosx5s <> Co-authored-by: kaosx5s <> Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * Removed transient line (#50) Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * Revving version for release (#51) Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> Co-authored-by: Corey Caverly <corey.caverly@autodesk.com> Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * Update docker-publish.yml (#52) * Update docker-publish.yml Removing Dockerhub and just doing ghcr Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * Revving for release Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * Decided to remove and not just comment out Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> --------- Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * retry on timeout (#54) * retry on timeout Signed-off-by: zliu3 <zejun_liu@intuit.com> * add unit test and throw exception on timeout Signed-off-by: zliu3 <zejun_liu@intuit.com> * update README.md Signed-off-by: zliu3 <zejun_liu@intuit.com> * add test to make sure retry on exception, add a good case Signed-off-by: zliu3 <zejun_liu@intuit.com> --------- Signed-off-by: zliu3 <zejun_liu@intuit.com> Co-authored-by: zliu3 <zejun_liu@intuit.com> Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * feat: drop support for older python versions, support up to 2.6.1 for pygithub (#56) * fix: drop support for outdated python * fix: update pygithub to support 2.2.0 or higher * fix: update tests for newer pytest * fix: check pygithub 2.4.0 support * fix: check pygithub 2.6.1 support * fix: set lazy to be false by default for backwards compatibility * fix: remove completed param for pygithub 2.6.0 support * fix: add required requester for pygithub 2.6.0 support * fix: fix param typo * fix: make requester a magicmock * fix: update tests for new lazy param * fix: update lazy from str to bool Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * revving for release Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * Revving python version for publish Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * Updating readme to reflect new location of docker images Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> * Revving for new release Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> --------- Signed-off-by: kaosx5s <jeremy.xfives@gmail.com> Signed-off-by: kaosx5s <> Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> Signed-off-by: zliu3 <zejun_liu@intuit.com> Co-authored-by: Jeremy <1028480+kaosx5s@users.noreply.github.com> Co-authored-by: zejunintuit <zejunliuajk@gmail.com> Co-authored-by: zliu3 <zejun_liu@intuit.com> * fix: typo in comment for github exception handling * fix: update github actions for newer setuppython verisons * fix: remove errant store_true on args --------- Signed-off-by: Corey Caverly <corey.caverly@autodesk.com> Signed-off-by: kaosx5s <jeremy.xfives@gmail.com> Signed-off-by: kaosx5s <> Signed-off-by: zliu3 <zejun_liu@intuit.com> Co-authored-by: kaosx5s <> Co-authored-by: Corey Caverly <coreycaverly@users.noreply.github.com> Co-authored-by: zejunintuit <zejunliuajk@gmail.com> Co-authored-by: zliu3 <zejun_liu@intuit.com>
1 parent 102d142 commit 8c22fdd

6 files changed

Lines changed: 184 additions & 36 deletions

File tree

.github/workflows/publish-python.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ jobs:
99
name: Create release-artifacts
1010
runs-on: ubuntu-latest
1111
steps:
12-
- uses: actions/checkout@v1
12+
- uses: actions/checkout@v6
1313
- name: Set up Python ${{ matrix.python-version }}
14-
uses: actions/setup-python@v1
14+
uses: actions/setup-python@v6
1515
with:
1616
python-version: 3.11
1717
- name: Install dependencies

.github/workflows/python-app.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ jobs:
1111
python-version: [3.9, '3.10', 3.11, 3.12, 3.13]
1212

1313
steps:
14-
- uses: actions/checkout@v1
14+
- uses: actions/checkout@v6
1515
- name: Set up Python ${{ matrix.python-version }}
16-
uses: actions/setup-python@v1
16+
uses: actions/setup-python@v6
1717
with:
1818
python-version: ${{ matrix.python-version }}
1919
- name: Install dependencies
@@ -25,5 +25,5 @@ jobs:
2525
pytest
2626
- name: Publish CodeCov
2727
uses: codecov/codecov-action@v1
28-
with:
28+
with:
2929
token: ${{ secrets.codecovToken }}

gordian/gordian.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,28 @@ def get_basic_parser():
8080
dest='pr_labels',
8181
help='List of space separated label names you wish to add to your pull request(s)'
8282
)
83+
parser.add_argument(
84+
'--draft',
85+
required=False,
86+
default=False,
87+
dest='draft',
88+
help='Create a draft pull request'
89+
)
90+
parser.add_argument(
91+
'--lazy',
92+
required=False,
93+
default=False,
94+
dest='lazy',
95+
help='Use lazy loading for the Github client'
96+
)
97+
parser.add_argument(
98+
'--pool-size',
99+
required=False,
100+
default=None,
101+
action='store',
102+
dest='pool_size',
103+
help='Pool size for the Github client'
104+
)
83105
parser.add_argument(
84106
'-F', '--file',
85107
required=False,
@@ -190,15 +212,23 @@ def transform(args, transformations, repositories, pr_description, pr_created_ca
190212
branch=args.branch,
191213
semver_label=args.semver_label,
192214
target_branch=args.target_branch,
193-
fork=args.fork
215+
fork=args.fork,
216+
lazy=args.lazy,
217+
pool_size=args.pool_size
194218
)
195219
for transformation in transformations:
196220
transformation(args, repo).run()
197221
if repo.dirty:
198222
repo.bump_version(args.dry_run)
199223
if not args.dry_run:
200224
try:
201-
pull_request = repo.create_pr(args.pr_message, pr_description, args.target_branch, args.pr_labels)
225+
pull_request = repo.create_pr(
226+
pr_message=args.pr_message,
227+
pr_body=pr_description,
228+
target_branch=args.target_branch,
229+
labels=args.pr_labels,
230+
draft=args.draft
231+
)
202232
pull_request_urls.append(pull_request.html_url)
203233
if pr_created_callback is not None:
204234
logger.debug(f'Calling post pr created callback with: {pull_request}, {repo.branch_name}')

gordian/repo.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from github import Github
2-
from github import GithubException
1+
from github import Github, GithubException, BadCredentialsException, RateLimitExceededException, UnknownObjectException
32
import datetime
43
import logging
54
import os
@@ -14,7 +13,7 @@
1413

1514
class Repo:
1615

17-
def __init__(self, repo_name, github_api_url=None, branch=None, github=None, files=None, semver_label=None, target_branch='master', fork=False, token=None, username=None, password=None, lazy=False):
16+
def __init__(self, repo_name, github_api_url=None, branch=None, github=None, files=None, semver_label=None, target_branch='master', fork=False, token=None, username=None, password=None, lazy=False, pool_size=None):
1817
if github_api_url is None:
1918
self.github_api_url = BASE_URL
2019
else:
@@ -29,10 +28,10 @@ def __init__(self, repo_name, github_api_url=None, branch=None, github=None, fil
2928

3029
if token:
3130
logger.debug('Using git token for authentication')
32-
self._github = Github(base_url=self.github_api_url, login_or_token=token, lazy=lazy)
31+
self._github = Github(base_url=self.github_api_url, login_or_token=token, lazy=lazy, pool_size=pool_size)
3332
else:
3433
logger.debug('Using git username and password for authentication')
35-
self._github = Github(base_url=self.github_api_url, login_or_token=username, password=password, lazy=lazy)
34+
self._github = Github(base_url=self.github_api_url, login_or_token=username, password=password, lazy=lazy, pool_size=pool_size)
3635

3736
if files is None:
3837
files = []
@@ -126,15 +125,26 @@ def _set_target_branch(self, target_branch, source_branch=None):
126125
self.branch_name = f"refs/heads/{datetime.datetime.now().strftime('%Y-%m-%d-%H%M%S.%f')}"
127126
self.source_branch = self.target_ref
128127

129-
@retry((GithubException, TimeoutError), tries=3, delay=1, backoff=2)
128+
@retry((GithubException, TimeoutError, RateLimitExceededException), tries=3, delay=1, backoff=2)
130129
def _get_repo_contents(self, path):
131130
try:
132131
logger.debug(f'Fetching repo contents {path}...')
133132
return self._source_repo.get_contents(path, self.target_branch)
133+
134+
# https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content--status-codes
135+
except BadCredentialsException as e:
136+
logger.info(f'Error fetching repo contents due to bad credentials: {e}')
137+
raise e
138+
except RateLimitExceededException as e:
139+
logger.info(f'Error fetching repo contents due to rate limit: {e}')
140+
raise e
141+
except UnknownObjectException as e:
142+
logger.info(f'Error fetching repo contents due to unknown object: {e}')
143+
raise e
134144
except GithubException as e:
135-
if e.status == 404:
136-
raise e
145+
# generic catch all for any other github exceptions
137146
logger.info(f'Error fetching repo contents: {e}')
147+
raise e
138148
except TimeoutError as e:
139149
logger.info(f'Error fetching repo contents: {e}')
140150
raise e
@@ -221,12 +231,14 @@ def delete_file(self, file, message, dry_run=False):
221231
branch=self.branch_name
222232
)
223233

224-
def create_pr(self, pr_message, pr_body, target_branch, labels=[]):
234+
def create_pr(self, pr_message, pr_body, target_branch, labels=[], draft=False):
235+
# https://github.com/PyGithub/PyGithub/blob/v2.8.1/github/Repository.py#L1822 , we need to stay in-line with the PyGithub library
225236
pr = self._target_repo.create_pull(
226-
pr_message,
227-
pr_body,
228-
target_branch,
229-
f'{self._source_repo.owner.login}:{self.branch_name}'
237+
title=pr_message,
238+
body=pr_body,
239+
base=target_branch,
240+
head=f'{self._source_repo.owner.login}:{self.branch_name}',
241+
draft=draft
230242
)
231243
if labels:
232244
pr.set_labels(*labels)

tests/test_gordian.py

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class TestGordian(unittest.TestCase):
88

99
class Args(object):
10-
def __init__(self, config_file='./tests/fixtures/test_config.yaml', dry_run = False):
10+
def __init__(self, config_file='./tests/fixtures/test_config.yaml', dry_run = False, lazy = False, pool_size = None, draft = False):
1111
self.config_file = config_file
1212
self.major = False
1313
self.minor = False
@@ -22,15 +22,18 @@ def __init__(self, config_file='./tests/fixtures/test_config.yaml', dry_run = Fa
2222
self.description = ''
2323
self.description_file = None
2424
self.fork = False
25+
self.lazy = lazy
26+
self.pool_size = pool_size
27+
self.draft = draft
2528

2629
def test_apply_transformations_without_changes(self):
2730
with patch('gordian.gordian.Repo') as RepoMock, patch('gordian.transformations.Transformation') as TransformationMockClass:
2831
instance = RepoMock.return_value
2932
instance.dirty = False
3033
apply_transformations(TestGordian.Args(), [TransformationMockClass])
3134
RepoMock.assert_has_calls([
32-
call('testOrg/TestService1', github_api_url=None, branch='test', semver_label=None, target_branch='master', fork=False),
33-
call('testOrg/TestService2', github_api_url=None, branch='test', semver_label=None, target_branch='master', fork=False)
35+
call('testOrg/TestService1', github_api_url=None, branch='test', semver_label=None, target_branch='master', fork=False, lazy=False, pool_size=None),
36+
call('testOrg/TestService2', github_api_url=None, branch='test', semver_label=None, target_branch='master', fork=False, lazy=False, pool_size=None)
3437
])
3538

3639
def test_apply_transformations_with_changes(self):
@@ -39,15 +42,49 @@ def test_apply_transformations_with_changes(self):
3942
instance.dirty = True
4043
apply_transformations(TestGordian.Args(), [TransformationMockClass])
4144
RepoMock.assert_has_calls([call().bump_version(False), call().bump_version(False)], any_order=True)
42-
RepoMock.assert_has_calls([call().create_pr('test', '', 'master', ANY), call().create_pr('test', '', 'master', ANY)], any_order=True)
45+
RepoMock.assert_has_calls([
46+
call().create_pr(pr_message='test', pr_body='', target_branch='master', labels=ANY, draft=False),
47+
call().create_pr(pr_message='test', pr_body='', target_branch='master', labels=ANY, draft=False)], any_order=True)
4348

4449
def test_apply_transformations_with_changes_dry_run(self):
4550
with patch('gordian.gordian.Repo') as RepoMock, patch('gordian.transformations.Transformation') as TransformationMockClass:
4651
instance = RepoMock.return_value
4752
instance.dirty = True
4853
apply_transformations(TestGordian.Args(dry_run=True), [TransformationMockClass])
4954
RepoMock.assert_has_calls([call().bump_version(True), call().bump_version(True)], any_order=True)
50-
self.assertNotIn(call().repo.create_pr('test', '', 'master', ANY), RepoMock.mock_calls)
55+
self.assertNotIn(call().repo.create_pr(pr_message='test', pr_body='', target_branch='master', labels=ANY, draft=False), RepoMock.mock_calls)
56+
57+
def test_apply_transformations_with_changes_draft(self):
58+
with patch('gordian.gordian.Repo') as RepoMock, patch('gordian.transformations.Transformation') as TransformationMockClass:
59+
instance = RepoMock.return_value
60+
instance.dirty = True
61+
apply_transformations(TestGordian.Args(draft=True), [TransformationMockClass])
62+
RepoMock.assert_has_calls([call().bump_version(False), call().bump_version(False)], any_order=True)
63+
RepoMock.assert_has_calls([
64+
call().create_pr(pr_message='test', pr_body='', target_branch='master', labels=ANY, draft=True),
65+
call().create_pr(pr_message='test', pr_body='', target_branch='master', labels=ANY, draft=True)], any_order=True)
66+
67+
def test_apply_transformations_with_changes_lazy(self):
68+
with patch('gordian.gordian.Repo') as RepoMock, patch('gordian.transformations.Transformation') as TransformationMockClass:
69+
instance = RepoMock.return_value
70+
instance.dirty = True
71+
apply_transformations(TestGordian.Args(lazy=True), [TransformationMockClass])
72+
RepoMock.assert_has_calls([call().bump_version(False), call().bump_version(False)], any_order=True)
73+
RepoMock.assert_has_calls([
74+
call('testOrg/TestService1', github_api_url=None, branch='test', semver_label=None, target_branch='master', fork=False, lazy=True, pool_size=None),
75+
call().create_pr(pr_message='test', pr_body='', target_branch='master', labels=ANY, draft=False),
76+
call().create_pr(pr_message='test', pr_body='', target_branch='master', labels=ANY, draft=False)], any_order=True)
77+
78+
def test_apply_transformations_with_changes_pool_size(self):
79+
with patch('gordian.gordian.Repo') as RepoMock, patch('gordian.transformations.Transformation') as TransformationMockClass:
80+
instance = RepoMock.return_value
81+
instance.dirty = True
82+
apply_transformations(TestGordian.Args(pool_size=10), [TransformationMockClass])
83+
RepoMock.assert_has_calls([call().bump_version(False), call().bump_version(False)], any_order=True)
84+
RepoMock.assert_has_calls([
85+
call('testOrg/TestService1', github_api_url=None, branch='test', semver_label=None, target_branch='master', fork=False, lazy=False, pool_size=10),
86+
call().create_pr(pr_message='test', pr_body='', target_branch='master', labels=ANY, draft=False),
87+
call().create_pr(pr_message='test', pr_body='', target_branch='master', labels=ANY, draft=False)], any_order=True)
5188

5289
def test_apply_transformations_with_changes_and_callback(self):
5390
with patch('gordian.gordian.Repo') as RepoMock, patch('gordian.transformations.Transformation') as TransformationMockClass:
@@ -60,8 +97,8 @@ def test_apply_transformations_with_changes_and_callback(self):
6097
pull_request = RepoMock.return_value.create_pr.return_value
6198
RepoMock.assert_has_calls([call().bump_version(False), call().bump_version(False)], any_order=True)
6299
RepoMock.assert_has_calls([
63-
call().create_pr('test', '', 'target_branch', ANY),
64-
call().create_pr('test', '', 'target_branch', ANY)],
100+
call().create_pr(pr_message='test', pr_body='', target_branch='target_branch', labels=ANY, draft=False),
101+
call().create_pr(pr_message='test', pr_body='', target_branch='target_branch', labels=ANY, draft=False)],
65102
any_order=True
66103
)
67104
callback_mock.assert_has_calls([
@@ -77,7 +114,9 @@ def test_apply_transformations_with_changes_default_labels(self):
77114
gordian_args.pr_labels = []
78115
apply_transformations(gordian_args, [TransformationMockClass])
79116
RepoMock.assert_has_calls([call().bump_version(False), call().bump_version(False)], any_order=True)
80-
RepoMock.assert_has_calls([call().create_pr('test', '', 'master', ANY), call().create_pr('test', '', 'master', ANY)], any_order=True)
117+
RepoMock.assert_has_calls([
118+
call().create_pr(pr_message='test', pr_body='', target_branch='master', labels=ANY, draft=False),
119+
call().create_pr(pr_message='test', pr_body='', target_branch='master', labels=ANY, draft=False)], any_order=True)
81120
self.assertNotIn(call()._repo.create_pull().set_labels(ANY), RepoMock.mock_calls)
82121

83122
def test_apply_transformations_with_changes_custom_description(self):
@@ -89,4 +128,6 @@ def test_apply_transformations_with_changes_custom_description(self):
89128
gordian_args.description_file = './tests/fixtures/pr_description'
90129
apply_transformations(gordian_args, [TransformationMockClass])
91130
RepoMock.assert_has_calls([call().bump_version(False), call().bump_version(False)], any_order=True)
92-
RepoMock.assert_has_calls([call().create_pr('test', description, 'master', ANY), call().create_pr('test', description, 'master', ANY)], any_order=True)
131+
RepoMock.assert_has_calls([
132+
call().create_pr(pr_message='test', pr_body=description, target_branch='master', labels=ANY, draft=False),
133+
call().create_pr(pr_message='test', pr_body=description, target_branch='master', labels=ANY, draft=False)], any_order=True)

0 commit comments

Comments
 (0)