Skip to content

Commit 3e1f5c9

Browse files
authored
Merge branch 'main' into notset-typing
2 parents 08222af + 193f699 commit 3e1f5c9

49 files changed

Lines changed: 1469 additions & 105 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ jobs:
3939
pip install tox tox-gh-actions
4040
- name: Run tests
4141
run: tox
42+
- name: Upload Test Results
43+
if: always()
44+
uses: actions/upload-artifact@v4
45+
with:
46+
name: Test Results (Python ${{ matrix.python-version }} on ${{ matrix.os-label }})
47+
path: pytest.xml
4248
- name: Upload coverage to Codecov
4349
uses: codecov/codecov-action@v3
4450

@@ -59,6 +65,16 @@ jobs:
5965
run: false
6066
shell: bash
6167

68+
event_file:
69+
name: "Event File"
70+
runs-on: ubuntu-latest
71+
steps:
72+
- name: Upload
73+
uses: actions/upload-artifact@v4
74+
with:
75+
name: Event File
76+
path: ${{ github.event_path }}
77+
6278
draft:
6379
runs-on: ubuntu-latest
6480
needs: test_success

.github/workflows/test-results.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Test Results
2+
3+
on:
4+
workflow_run:
5+
workflows: ["CI"]
6+
types:
7+
- completed
8+
permissions: {}
9+
10+
jobs:
11+
test-results:
12+
name: Test Results
13+
if: github.event.workflow_run.conclusion != 'skipped'
14+
runs-on: ubuntu-latest
15+
permissions:
16+
checks: write
17+
pull-requests: write
18+
19+
steps:
20+
- name: Download and Extract Artifacts
21+
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d
22+
with:
23+
run_id: ${{ github.event.workflow_run.id }}
24+
path: artifacts
25+
26+
- name: Publish Test Results
27+
id: test-results
28+
uses: EnricoMi/publish-unit-test-result-action@v2
29+
with:
30+
commit: ${{ github.event.workflow_run.head_sha }}
31+
event_file: artifacts/Event File/event.json
32+
event_name: ${{ github.event.workflow_run.event }}
33+
files: "artifacts/**/*.xml"

github/Auth.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import time
3232
from abc import ABC
3333
from datetime import datetime, timedelta, timezone
34-
from typing import TYPE_CHECKING, Dict, Optional, Union
34+
from typing import TYPE_CHECKING, Callable, Dict, Optional, Union
3535

3636
import jwt
3737
from requests import utils
@@ -43,6 +43,9 @@
4343
if TYPE_CHECKING:
4444
from github.GithubIntegration import GithubIntegration
4545

46+
PrivateKeyGenerator = Callable[[], Union[str, bytes]]
47+
DictSignFunction = Callable[[dict], Union[str, bytes]]
48+
4649
# For App authentication, time remaining before token expiration to request a new one
4750
ACCESS_TOKEN_REFRESH_THRESHOLD_SECONDS = 20
4851
TOKEN_REFRESH_THRESHOLD_TIMEDELTA = timedelta(seconds=ACCESS_TOKEN_REFRESH_THRESHOLD_SECONDS)
@@ -190,36 +193,49 @@ class AppAuth(JWT):
190193
191194
"""
192195

196+
@staticmethod
197+
def create_jwt_sign(private_key_or_func: Union[str, PrivateKeyGenerator], jwt_algorithm: str) -> DictSignFunction:
198+
def jwt_sign(payload: dict) -> Union[str, bytes]:
199+
if callable(private_key_or_func):
200+
private_key = private_key_or_func()
201+
else:
202+
private_key = private_key_or_func
203+
return jwt.encode(payload, key=private_key, algorithm=jwt_algorithm)
204+
205+
return jwt_sign
206+
207+
# v3: move * above private_key
193208
def __init__(
194209
self,
195210
app_id: Union[int, str],
196-
private_key: str,
211+
private_key: Optional[Union[str, PrivateKeyGenerator]] = None,
212+
*,
213+
sign_func: Optional[DictSignFunction] = None,
197214
jwt_expiry: int = Consts.DEFAULT_JWT_EXPIRY,
198215
jwt_issued_at: int = Consts.DEFAULT_JWT_ISSUED_AT,
199-
jwt_algorithm: str = Consts.DEFAULT_JWT_ALGORITHM,
200216
):
201217
assert isinstance(app_id, (int, str)), app_id
202218
if isinstance(app_id, str):
203219
assert len(app_id) > 0, "app_id must not be empty"
204-
assert isinstance(private_key, str)
205-
assert len(private_key) > 0, "private_key must not be empty"
220+
assert private_key is not None or sign_func is not None, "either private_key or sign_func must be given"
221+
assert private_key is None or sign_func is None, "private_key or sign_func cannot both be given"
222+
if private_key is not None:
223+
assert isinstance(private_key, str) or callable(private_key)
224+
if isinstance(private_key, str):
225+
assert len(private_key) > 0, "private_key must not be empty"
226+
sign_func = AppAuth.create_jwt_sign(private_key, Consts.DEFAULT_JWT_ALGORITHM)
206227
assert isinstance(jwt_expiry, int), jwt_expiry
207228
assert Consts.MIN_JWT_EXPIRY <= jwt_expiry <= Consts.MAX_JWT_EXPIRY, jwt_expiry
208229

209230
self._app_id = app_id
210-
self._private_key = private_key
231+
self._sign_func = sign_func
211232
self._jwt_expiry = jwt_expiry
212233
self._jwt_issued_at = jwt_issued_at
213-
self._jwt_algorithm = jwt_algorithm
214234

215235
@property
216236
def app_id(self) -> Union[int, str]:
217237
return self._app_id
218238

219-
@property
220-
def private_key(self) -> str:
221-
return self._private_key
222-
223239
@property
224240
def token(self) -> str:
225241
return self.create_jwt()
@@ -258,7 +274,8 @@ def create_jwt(self, expiration: Optional[int] = None) -> str:
258274
"exp": now + (expiration if expiration is not None else self._jwt_expiry),
259275
"iss": self._app_id,
260276
}
261-
encrypted = jwt.encode(payload, key=self.private_key, algorithm=self._jwt_algorithm)
277+
assert self._sign_func is not None
278+
encrypted = self._sign_func(payload)
262279

263280
if isinstance(encrypted, bytes):
264281
return encrypted.decode("utf-8")
@@ -331,10 +348,6 @@ def withRequester(self, requester: Requester) -> "AppInstallationAuth":
331348
def app_id(self) -> Union[int, str]:
332349
return self._app_auth.app_id
333350

334-
@property
335-
def private_key(self) -> str:
336-
return self._app_auth.private_key
337-
338351
@property
339352
def installation_id(self) -> int:
340353
return self._installation_id

github/CodeSecurityConfig.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
############################ Copyrights and license ############################
2+
# #
3+
# Copyright 2024 Bill Napier <napier@pobox.com> #
4+
# #
5+
# This file is part of PyGithub. #
6+
# http://pygithub.readthedocs.io/ #
7+
# #
8+
# PyGithub is free software: you can redistribute it and/or modify it under #
9+
# the terms of the GNU Lesser General Public License as published by the Free #
10+
# Software Foundation, either version 3 of the License, or (at your option) #
11+
# any later version. #
12+
# #
13+
# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY #
14+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
15+
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more #
16+
# details. #
17+
# #
18+
# You should have received a copy of the GNU Lesser General Public License #
19+
# along with PyGithub. If not, see <http://www.gnu.org/licenses/>. #
20+
# #
21+
################################################################################
22+
23+
from __future__ import annotations
24+
25+
from datetime import datetime
26+
from typing import Any
27+
28+
from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet
29+
30+
31+
class CodeSecurityConfig(NonCompletableGithubObject):
32+
"""
33+
This class represents Configurations for Code Security.
34+
35+
The reference can be found here
36+
https://docs.github.com/en/rest/code-security/configurations.
37+
38+
"""
39+
40+
def _initAttributes(self) -> None:
41+
self._id: Attribute[int] = NotSet
42+
self._name: Attribute[str] = NotSet
43+
self._advanced_security: Attribute[str] = NotSet
44+
self._code_scanning_default_setup: Attribute[str] = NotSet
45+
self._created_at: Attribute[datetime] = NotSet
46+
self._dependabot_alerts: Attribute[str] = NotSet
47+
self._dependabot_security_updates: Attribute[str] = NotSet
48+
self._dependency_graph: Attribute[str] = NotSet
49+
self._dependency_graph_autosubmit_action: Attribute[str] = NotSet
50+
self._description: Attribute[str] = NotSet
51+
self._enforcement: Attribute[str] = NotSet
52+
self._html_url: Attribute[str] = NotSet
53+
self._private_vulnerability_reporting: Attribute[str] = NotSet
54+
self._secret_scanning: Attribute[str] = NotSet
55+
self._secret_scanning_delegated_bypass: Attribute[str] = NotSet
56+
self._secret_scanning_non_provider_patterns: Attribute[str] = NotSet
57+
self._secret_scanning_push_protection: Attribute[str] = NotSet
58+
self._secret_scanning_validity_checks: Attribute[str] = NotSet
59+
self._target_type: Attribute[str] = NotSet
60+
self._url: Attribute[str] = NotSet
61+
self._updated_at: Attribute[datetime] = NotSet
62+
63+
def __repr__(self) -> str:
64+
return self.get__repr__(
65+
{
66+
"id": self.id,
67+
"name": self.name,
68+
"description": self.description,
69+
}
70+
)
71+
72+
@property
73+
def advanced_security(self) -> str:
74+
return self._advanced_security.value
75+
76+
@property
77+
def code_scanning_default_setup(self) -> str:
78+
return self._code_scanning_default_setup.value
79+
80+
@property
81+
def created_at(self) -> datetime:
82+
return self._created_at.value
83+
84+
@property
85+
def dependabot_alerts(self) -> str:
86+
return self._dependabot_alerts.value
87+
88+
@property
89+
def dependabot_security_updates(self) -> str:
90+
return self._dependabot_security_updates.value
91+
92+
@property
93+
def dependency_graph(self) -> str:
94+
return self._dependency_graph.value
95+
96+
@property
97+
def dependency_graph_autosubmit_action(self) -> str:
98+
return self._dependency_graph_autosubmit_action.value
99+
100+
@property
101+
def description(self) -> str:
102+
return self._description.value
103+
104+
@property
105+
def enforcement(self) -> str:
106+
return self._enforcement.value
107+
108+
@property
109+
def html_url(self) -> str:
110+
return self._html_url.value
111+
112+
@property
113+
def id(self) -> int:
114+
return self._id.value
115+
116+
@property
117+
def name(self) -> str:
118+
return self._name.value
119+
120+
@property
121+
def private_vulnerability_reporting(self) -> str:
122+
return self._private_vulnerability_reporting.value
123+
124+
@property
125+
def secret_scanning(self) -> str:
126+
return self._secret_scanning.value
127+
128+
@property
129+
def secret_scanning_delegated_bypass(self) -> str:
130+
return self._secret_scanning_delegated_bypass.value
131+
132+
@property
133+
def secret_scanning_non_provider_patterns(self) -> str:
134+
return self._secret_scanning_non_provider_patterns.value
135+
136+
@property
137+
def secret_scanning_push_protection(self) -> str:
138+
return self._secret_scanning_push_protection.value
139+
140+
@property
141+
def secret_scanning_validity_checks(self) -> str:
142+
return self._secret_scanning_validity_checks.value
143+
144+
@property
145+
def target_type(self) -> str:
146+
return self._target_type.value
147+
148+
@property
149+
def updated_at(self) -> datetime:
150+
return self._updated_at.value
151+
152+
@property
153+
def url(self) -> str:
154+
return self._url.value
155+
156+
def _useAttributes(self, attributes: dict[str, Any]) -> None:
157+
if "advanced_security" in attributes: # pragma no branch
158+
self._advanced_security = self._makeStringAttribute(attributes["advanced_security"])
159+
if "code_scanning_default_setup" in attributes: # pragma no branch
160+
self._code_scanning_default_setup = self._makeStringAttribute(attributes["code_scanning_default_setup"])
161+
if "created_at" in attributes: # pragma no branch
162+
assert attributes["created_at"] is None or isinstance(attributes["created_at"], str), attributes[
163+
"created_at"
164+
]
165+
self._created_at = self._makeDatetimeAttribute(attributes["created_at"])
166+
if "dependabot_alerts" in attributes: # pragma no branch
167+
self._dependabot_alerts = self._makeStringAttribute(attributes["dependabot_alerts"])
168+
if "dependabot_security_updates" in attributes: # pragma no branch
169+
self._dependabot_security_updates = self._makeStringAttribute(attributes["dependabot_security_updates"])
170+
if "dependency_graph" in attributes: # pragma no branch
171+
self._dependency_graph = self._makeStringAttribute(attributes["dependency_graph"])
172+
if "dependency_graph_autosubmit_action" in attributes: # pragma no branch
173+
self._dependency_graph_autosubmit_action = self._makeStringAttribute(
174+
attributes["dependency_graph_autosubmit_action"]
175+
)
176+
if "description" in attributes: # pragma no branch
177+
self._description = self._makeStringAttribute(attributes["description"])
178+
if "enforcement" in attributes: # pragma no branch
179+
self._enforcement = self._makeStringAttribute(attributes["enforcement"])
180+
if "html_url" in attributes: # pragma no branch
181+
self._html_url = self._makeStringAttribute(attributes["html_url"])
182+
if "id" in attributes: # pragma no branch
183+
self._id = self._makeIntAttribute(attributes["id"])
184+
if "name" in attributes: # pragma no branch
185+
self._name = self._makeStringAttribute(attributes["name"])
186+
if "private_vulnerability_reporting" in attributes: # pragma no branch
187+
self._private_vulnerability_reporting = self._makeStringAttribute(
188+
attributes["private_vulnerability_reporting"]
189+
)
190+
if "secret_scanning" in attributes: # pragma no branch
191+
self._secret_scanning = self._makeStringAttribute(attributes["secret_scanning"])
192+
if "secret_scanning_delegated_bypass" in attributes: # pragma no branch
193+
self._secret_scanning_delegated_bypass = self._makeStringAttribute(
194+
attributes["secret_scanning_delegated_bypass"]
195+
)
196+
if "secret_scanning_non_provider_patterns" in attributes: # pragma no branch
197+
self._secret_scanning_non_provider_patterns = self._makeStringAttribute(
198+
attributes["secret_scanning_non_provider_patterns"]
199+
)
200+
if "secret_scanning_push_protection" in attributes: # pragma no branch
201+
self._secret_scanning_push_protection = self._makeStringAttribute(
202+
attributes["secret_scanning_push_protection"]
203+
)
204+
if "secret_scanning_validity_checks" in attributes: # pragma no branch
205+
self._secret_scanning_validity_checks = self._makeStringAttribute(
206+
attributes["secret_scanning_validity_checks"]
207+
)
208+
if "target_type" in attributes: # pragma no branch
209+
self._target_type = self._makeStringAttribute(attributes["target_type"])
210+
if "updated_at" in attributes: # pragma no branch
211+
assert attributes["updated_at"] is None or isinstance(attributes["updated_at"], str), attributes[
212+
"updated_at"
213+
]
214+
self._updated_at = self._makeDatetimeAttribute(attributes["updated_at"])
215+
if "url" in attributes: # pragma no branch
216+
self._url = self._makeStringAttribute(attributes["url"])

github/Commit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class Commit(CompletableGithubObject):
8282
This class represents Commits.
8383
8484
The reference can be found here
85-
https://docs.github.com/en/rest/reference/git#commits
85+
https://docs.github.com/en/rest/commits/commits#get-a-commit-object
8686
8787
The OpenAPI schema can be found at
8888
- /components/schemas/branch-short/properties/commit

0 commit comments

Comments
 (0)