githubkit 0.11.0a0__py3-none-any.whl → 0.11.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- githubkit/__init__.py +5 -0
- githubkit/auth/_url.py +19 -2
- githubkit/auth/app.py +35 -16
- githubkit/compat.py +110 -0
- githubkit/config.py +8 -1
- githubkit/core.py +28 -3
- githubkit/github.py +9 -3
- githubkit/graphql/models.py +6 -6
- githubkit/lazy_module.py +120 -0
- githubkit/response.py +3 -2
- githubkit/rest/__init__.py +14136 -185
- githubkit/typing.py +32 -21
- githubkit/utils.py +40 -3
- githubkit/versions/__init__.py +19 -0
- githubkit/versions/latest/models.py +14147 -0
- githubkit/versions/latest/types.py +14283 -0
- githubkit/versions/latest/webhooks.py +705 -0
- githubkit/versions/rest.py +55 -0
- githubkit/versions/v2022_11_28/models/__init__.py +95086 -0
- githubkit/versions/v2022_11_28/rest/__init__.py +265 -0
- githubkit/{rest → versions/v2022_11_28/rest}/actions.py +1457 -831
- githubkit/{rest → versions/v2022_11_28/rest}/activity.py +472 -220
- githubkit/{rest → versions/v2022_11_28/rest}/apps.py +470 -235
- githubkit/{rest → versions/v2022_11_28/rest}/billing.py +47 -18
- githubkit/{rest → versions/v2022_11_28/rest}/checks.py +219 -131
- githubkit/{rest → versions/v2022_11_28/rest}/classroom.py +86 -38
- githubkit/{rest → versions/v2022_11_28/rest}/code_scanning.py +316 -122
- githubkit/{rest → versions/v2022_11_28/rest}/codes_of_conduct.py +29 -10
- githubkit/{rest → versions/v2022_11_28/rest}/codespaces.py +726 -345
- githubkit/{rest → versions/v2022_11_28/rest}/copilot.py +144 -96
- githubkit/{rest → versions/v2022_11_28/rest}/dependabot.py +262 -155
- githubkit/{rest → versions/v2022_11_28/rest}/dependency_graph.py +63 -34
- githubkit/{rest → versions/v2022_11_28/rest}/emojis.py +17 -8
- githubkit/{rest → versions/v2022_11_28/rest}/gists.py +257 -131
- githubkit/{rest → versions/v2022_11_28/rest}/git.py +194 -114
- githubkit/{rest → versions/v2022_11_28/rest}/gitignore.py +25 -10
- githubkit/{rest → versions/v2022_11_28/rest}/interactions.py +135 -56
- githubkit/{rest → versions/v2022_11_28/rest}/issues.py +913 -414
- githubkit/{rest → versions/v2022_11_28/rest}/licenses.py +47 -16
- githubkit/{rest → versions/v2022_11_28/rest}/markdown.py +37 -33
- githubkit/{rest → versions/v2022_11_28/rest}/meta.py +44 -17
- githubkit/{rest → versions/v2022_11_28/rest}/migrations.py +322 -163
- githubkit/{rest → versions/v2022_11_28/rest}/oidc.py +34 -19
- githubkit/{rest → versions/v2022_11_28/rest}/orgs.py +2453 -703
- githubkit/{rest → versions/v2022_11_28/rest}/packages.py +237 -84
- githubkit/{rest → versions/v2022_11_28/rest}/projects.py +468 -244
- githubkit/{rest → versions/v2022_11_28/rest}/pulls.py +561 -296
- githubkit/{rest → versions/v2022_11_28/rest}/rate_limit.py +17 -8
- githubkit/{rest → versions/v2022_11_28/rest}/reactions.py +399 -227
- githubkit/{rest → versions/v2022_11_28/rest}/repos.py +3297 -1565
- githubkit/{rest → versions/v2022_11_28/rest}/search.py +141 -71
- githubkit/{rest → versions/v2022_11_28/rest}/secret_scanning.py +164 -64
- githubkit/{rest → versions/v2022_11_28/rest}/security_advisories.py +239 -91
- githubkit/{rest → versions/v2022_11_28/rest}/teams.py +774 -448
- githubkit/{rest → versions/v2022_11_28/rest}/users.py +552 -266
- githubkit/versions/v2022_11_28/types/__init__.py +74008 -0
- githubkit/versions/v2022_11_28/webhooks/__init__.py +406 -0
- githubkit/versions/v2022_11_28/webhooks/_namespace.py +1314 -0
- githubkit/versions/v2022_11_28/webhooks/_types.py +395 -0
- githubkit/versions/v2022_11_28/webhooks/branch_protection_configuration.py +45 -0
- githubkit/versions/v2022_11_28/webhooks/branch_protection_rule.py +48 -0
- githubkit/versions/v2022_11_28/webhooks/check_run.py +46 -0
- githubkit/versions/v2022_11_28/webhooks/check_suite.py +43 -0
- githubkit/versions/v2022_11_28/webhooks/code_scanning_alert.py +57 -0
- githubkit/versions/v2022_11_28/webhooks/commit_comment.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/create.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/custom_property.py +48 -0
- githubkit/versions/v2022_11_28/webhooks/custom_property_values.py +33 -0
- githubkit/versions/v2022_11_28/webhooks/delete.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/dependabot_alert.py +60 -0
- githubkit/versions/v2022_11_28/webhooks/deploy_key.py +37 -0
- githubkit/versions/v2022_11_28/webhooks/deployment.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/deployment_protection_rule.py +33 -0
- githubkit/versions/v2022_11_28/webhooks/deployment_review.py +48 -0
- githubkit/versions/v2022_11_28/webhooks/deployment_status.py +33 -0
- githubkit/versions/v2022_11_28/webhooks/discussion.py +79 -0
- githubkit/versions/v2022_11_28/webhooks/discussion_comment.py +48 -0
- githubkit/versions/v2022_11_28/webhooks/fork.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/github_app_authorization.py +33 -0
- githubkit/versions/v2022_11_28/webhooks/gollum.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/installation.py +49 -0
- githubkit/versions/v2022_11_28/webhooks/installation_repositories.py +45 -0
- githubkit/versions/v2022_11_28/webhooks/installation_target.py +33 -0
- githubkit/versions/v2022_11_28/webhooks/issue_comment.py +43 -0
- githubkit/versions/v2022_11_28/webhooks/issues.py +82 -0
- githubkit/versions/v2022_11_28/webhooks/label.py +39 -0
- githubkit/versions/v2022_11_28/webhooks/marketplace_purchase.py +54 -0
- githubkit/versions/v2022_11_28/webhooks/member.py +39 -0
- githubkit/versions/v2022_11_28/webhooks/membership.py +37 -0
- githubkit/versions/v2022_11_28/webhooks/merge_group.py +37 -0
- githubkit/versions/v2022_11_28/webhooks/meta.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/milestone.py +49 -0
- githubkit/versions/v2022_11_28/webhooks/org_block.py +37 -0
- githubkit/versions/v2022_11_28/webhooks/organization.py +49 -0
- githubkit/versions/v2022_11_28/webhooks/package.py +37 -0
- githubkit/versions/v2022_11_28/webhooks/page_build.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/personal_access_token_request.py +51 -0
- githubkit/versions/v2022_11_28/webhooks/ping.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/project.py +49 -0
- githubkit/versions/v2022_11_28/webhooks/project_card.py +49 -0
- githubkit/versions/v2022_11_28/webhooks/project_column.py +46 -0
- githubkit/versions/v2022_11_28/webhooks/projects_v2.py +49 -0
- githubkit/versions/v2022_11_28/webhooks/projects_v2_item.py +60 -0
- githubkit/versions/v2022_11_28/webhooks/public.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/pull_request.py +130 -0
- githubkit/versions/v2022_11_28/webhooks/pull_request_review.py +48 -0
- githubkit/versions/v2022_11_28/webhooks/pull_request_review_comment.py +48 -0
- githubkit/versions/v2022_11_28/webhooks/pull_request_review_thread.py +45 -0
- githubkit/versions/v2022_11_28/webhooks/push.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/registry_package.py +42 -0
- githubkit/versions/v2022_11_28/webhooks/release.py +55 -0
- githubkit/versions/v2022_11_28/webhooks/repository.py +61 -0
- githubkit/versions/v2022_11_28/webhooks/repository_advisory.py +45 -0
- githubkit/versions/v2022_11_28/webhooks/repository_dispatch.py +33 -0
- githubkit/versions/v2022_11_28/webhooks/repository_import.py +33 -0
- githubkit/versions/v2022_11_28/webhooks/repository_ruleset.py +48 -0
- githubkit/versions/v2022_11_28/webhooks/repository_vulnerability_alert.py +51 -0
- githubkit/versions/v2022_11_28/webhooks/secret_scanning_alert.py +51 -0
- githubkit/versions/v2022_11_28/webhooks/secret_scanning_alert_location.py +33 -0
- githubkit/versions/v2022_11_28/webhooks/security_advisory.py +48 -0
- githubkit/versions/v2022_11_28/webhooks/security_and_analysis.py +33 -0
- githubkit/versions/v2022_11_28/webhooks/sponsorship.py +52 -0
- githubkit/versions/v2022_11_28/webhooks/star.py +37 -0
- githubkit/versions/v2022_11_28/webhooks/status.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/team.py +49 -0
- githubkit/versions/v2022_11_28/webhooks/team_add.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/watch.py +28 -0
- githubkit/versions/v2022_11_28/webhooks/workflow_dispatch.py +33 -0
- githubkit/versions/v2022_11_28/webhooks/workflow_job.py +46 -0
- githubkit/versions/v2022_11_28/webhooks/workflow_run.py +43 -0
- githubkit/versions/webhooks.py +50 -0
- githubkit/webhooks/__init__.py +21 -8
- {githubkit-0.11.0a0.dist-info → githubkit-0.11.0a1.dist-info}/METADATA +119 -17
- githubkit-0.11.0a1.dist-info/RECORD +149 -0
- {githubkit-0.11.0a0.dist-info → githubkit-0.11.0a1.dist-info}/WHEEL +1 -1
- githubkit/rest/models.py +0 -22271
- githubkit/rest/types.py +0 -15068
- githubkit/webhooks/models.py +0 -18290
- githubkit/webhooks/parse.py +0 -27
- githubkit/webhooks/types.py +0 -1171
- githubkit/webhooks/verify.py +0 -81
- githubkit-0.11.0a0.dist-info/RECORD +0 -70
- {githubkit-0.11.0a0.dist-info → githubkit-0.11.0a1.dist-info}/LICENSE +0 -0
githubkit/__init__.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
from . import lazy_module
|
|
2
|
+
|
|
3
|
+
lazy_module.apply()
|
|
4
|
+
|
|
1
5
|
from .config import Config as Config
|
|
2
6
|
from .github import GitHub as GitHub
|
|
3
7
|
from .core import GitHubCore as GitHubCore
|
|
4
8
|
from .response import Response as Response
|
|
5
9
|
from .paginator import Paginator as Paginator
|
|
10
|
+
from .compat import GitHubModel as GitHubModel
|
|
6
11
|
from .auth import AppAuthStrategy as AppAuthStrategy
|
|
7
12
|
from .auth import BaseAuthStrategy as BaseAuthStrategy
|
|
8
13
|
from .auth import TokenAuthStrategy as TokenAuthStrategy
|
githubkit/auth/_url.py
CHANGED
|
@@ -26,21 +26,38 @@ APP_ROUTES = {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
BYPASS_REGEX = re.compile(r"/login/(oauth/access_token|device/code)$")
|
|
29
|
-
|
|
29
|
+
"""Regex to match routes that should bypass auth.
|
|
30
|
+
|
|
31
|
+
See: https://github.com/octokit/auth-oauth-user.js/blob/b8b1141b21f285027e36a4a1670163e4db51d30f/src/hook.ts#L44
|
|
32
|
+
"""
|
|
33
|
+
APP_AUTH_REGEX = re.compile(rf"^(?:{'|'.join(APP_ROUTES)})$", re.I)
|
|
34
|
+
"""Regex to match app authentication routes.
|
|
35
|
+
|
|
36
|
+
See: https://github.com/octokit/auth-app.js/blob/c0068e06081a5d930799285a7c79c9c948b676b6/src/requires-app-auth.ts#L45
|
|
37
|
+
"""
|
|
30
38
|
BASIC_AUTH_REGEX = re.compile(r"/applications/[^/]+/(token|grant)s?")
|
|
39
|
+
"""Regex to match basic authentication routes.
|
|
40
|
+
|
|
41
|
+
See: https://github.com/octokit/auth-oauth-user.js/blob/b8b1141b21f285027e36a4a1670163e4db51d30f/src/requires-basic-auth.ts#L17
|
|
42
|
+
"""
|
|
31
43
|
OAUTH_BASE_REGEX = re.compile(r"^https://(api\.)?github\.com/?$")
|
|
32
44
|
|
|
33
45
|
|
|
34
46
|
def require_bypass(url: httpx.URL) -> bool:
|
|
35
|
-
|
|
47
|
+
"""Check if the request should bypass auth."""
|
|
36
48
|
return bool(BYPASS_REGEX.search(url.path))
|
|
37
49
|
|
|
38
50
|
|
|
39
51
|
def require_app_auth(url: httpx.URL) -> bool:
|
|
52
|
+
"""Check if the request should use app authentication.
|
|
53
|
+
|
|
54
|
+
Note: the input url must remove the base url first.
|
|
55
|
+
"""
|
|
40
56
|
return bool(APP_AUTH_REGEX.search(url.path))
|
|
41
57
|
|
|
42
58
|
|
|
43
59
|
def require_basic_auth(url: httpx.URL) -> bool:
|
|
60
|
+
"""Check if the request should use basic authentication."""
|
|
44
61
|
return bool(BASIC_AUTH_REGEX.search(url.path))
|
|
45
62
|
|
|
46
63
|
|
githubkit/auth/app.py
CHANGED
|
@@ -7,12 +7,7 @@ import httpx
|
|
|
7
7
|
from githubkit.exception import AuthCredentialError
|
|
8
8
|
from githubkit.cache import DEFAULT_CACHE, BaseCache
|
|
9
9
|
from githubkit.utils import UNSET, Unset, exclude_unset
|
|
10
|
-
from githubkit.
|
|
11
|
-
BasicError,
|
|
12
|
-
ValidationError,
|
|
13
|
-
InstallationToken,
|
|
14
|
-
AppInstallationsInstallationIdAccessTokensPostBody,
|
|
15
|
-
)
|
|
10
|
+
from githubkit.compat import model_dump, type_validate_python
|
|
16
11
|
|
|
17
12
|
from .base import BaseAuthStrategy
|
|
18
13
|
from .oauth import OAuthAppAuthStrategy
|
|
@@ -25,7 +20,8 @@ except ImportError:
|
|
|
25
20
|
|
|
26
21
|
if TYPE_CHECKING:
|
|
27
22
|
from githubkit import Response, GitHubCore
|
|
28
|
-
from githubkit.
|
|
23
|
+
from githubkit.versions.latest.models import InstallationToken
|
|
24
|
+
from githubkit.versions.latest.types import AppPermissionsType
|
|
29
25
|
|
|
30
26
|
|
|
31
27
|
@dataclass
|
|
@@ -49,6 +45,16 @@ class AppAuth(httpx.Auth):
|
|
|
49
45
|
"{installation_id}:{permissions}:{repositories}:{repository_ids}"
|
|
50
46
|
)
|
|
51
47
|
|
|
48
|
+
def _get_api_route(self, url: httpx.URL) -> httpx.URL:
|
|
49
|
+
"""Get the api route (path only) for the given url."""
|
|
50
|
+
base_url_path = self.github.config.base_url.path
|
|
51
|
+
if (path := url.path).startswith(base_url_path):
|
|
52
|
+
path = path[len(base_url_path) :]
|
|
53
|
+
# add leading slash
|
|
54
|
+
if not path.startswith("/"):
|
|
55
|
+
path = "/" + path
|
|
56
|
+
return httpx.URL(path=path)
|
|
57
|
+
|
|
52
58
|
def _create_jwt(self) -> str:
|
|
53
59
|
"""Create a JWT authenticating as GitHub APP.
|
|
54
60
|
|
|
@@ -80,6 +86,10 @@ class AppAuth(httpx.Auth):
|
|
|
80
86
|
return token
|
|
81
87
|
|
|
82
88
|
def _build_installation_auth_request(self) -> httpx.Request:
|
|
89
|
+
from githubkit.versions.latest.models import (
|
|
90
|
+
AppInstallationsInstallationIdAccessTokensPostBody,
|
|
91
|
+
)
|
|
92
|
+
|
|
83
93
|
if self.installation_id is UNSET:
|
|
84
94
|
raise AuthCredentialError(
|
|
85
95
|
"GitHub APP installation_id must be provided "
|
|
@@ -91,13 +101,16 @@ class AppAuth(httpx.Auth):
|
|
|
91
101
|
raw_path=base_url.raw_path
|
|
92
102
|
+ f"app/installations/{self.installation_id}/access_tokens".encode("ascii")
|
|
93
103
|
)
|
|
94
|
-
body =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
body = model_dump(
|
|
105
|
+
type_validate_python(
|
|
106
|
+
AppInstallationsInstallationIdAccessTokensPostBody,
|
|
107
|
+
{
|
|
108
|
+
"repositories": self.repositories,
|
|
109
|
+
"repository_ids": self.repository_ids,
|
|
110
|
+
"permissions": self.permissions,
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
)
|
|
101
114
|
return httpx.Request(
|
|
102
115
|
"POST",
|
|
103
116
|
url,
|
|
@@ -111,6 +124,12 @@ class AppAuth(httpx.Auth):
|
|
|
111
124
|
def _parse_installation_auth_response(
|
|
112
125
|
self, response: httpx.Response
|
|
113
126
|
) -> "Response[InstallationToken]":
|
|
127
|
+
from githubkit.versions.latest.models import (
|
|
128
|
+
BasicError,
|
|
129
|
+
ValidationError,
|
|
130
|
+
InstallationToken,
|
|
131
|
+
)
|
|
132
|
+
|
|
114
133
|
return self.github._check(
|
|
115
134
|
response,
|
|
116
135
|
response_model=InstallationToken,
|
|
@@ -150,7 +169,7 @@ class AppAuth(httpx.Auth):
|
|
|
150
169
|
if require_bypass(request.url):
|
|
151
170
|
yield request
|
|
152
171
|
return
|
|
153
|
-
if require_app_auth(request.url):
|
|
172
|
+
if require_app_auth(self._get_api_route(request.url)):
|
|
154
173
|
request.headers["Authorization"] = f"Bearer {self.get_jwt()}"
|
|
155
174
|
yield request
|
|
156
175
|
return
|
|
@@ -186,7 +205,7 @@ class AppAuth(httpx.Auth):
|
|
|
186
205
|
if require_bypass(request.url):
|
|
187
206
|
yield request
|
|
188
207
|
return
|
|
189
|
-
if require_app_auth(request.url):
|
|
208
|
+
if require_app_auth(self._get_api_route(request.url)):
|
|
190
209
|
request.headers["Authorization"] = f"Bearer {await self.aget_jwt()}"
|
|
191
210
|
yield request
|
|
192
211
|
return
|
githubkit/compat.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
TYPE_CHECKING,
|
|
3
|
+
Any,
|
|
4
|
+
Dict,
|
|
5
|
+
Type,
|
|
6
|
+
TypeVar,
|
|
7
|
+
Callable,
|
|
8
|
+
Protocol,
|
|
9
|
+
Generator,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from pydantic import VERSION
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
|
|
16
|
+
PYDANTIC_V2 = int(VERSION.split(".", 1)[0]) == 2
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
|
|
20
|
+
class ModelBeforeValidator(Protocol):
|
|
21
|
+
def __call__(self, cls: Any, __value: Any) -> Any:
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
class CustomValidationClass(Protocol):
|
|
25
|
+
@classmethod
|
|
26
|
+
def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
CVC = TypeVar("CVC", bound=CustomValidationClass)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if PYDANTIC_V2:
|
|
33
|
+
from pydantic_core import CoreSchema, core_schema
|
|
34
|
+
from pydantic import (
|
|
35
|
+
BaseModel,
|
|
36
|
+
ConfigDict,
|
|
37
|
+
TypeAdapter,
|
|
38
|
+
GetCoreSchemaHandler,
|
|
39
|
+
model_validator,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
class GitHubModel(BaseModel):
|
|
43
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
44
|
+
|
|
45
|
+
class ExtraGitHubModel(GitHubModel):
|
|
46
|
+
model_config = ConfigDict(extra="allow")
|
|
47
|
+
|
|
48
|
+
def type_validate_python(type_: Type[T], data: Any) -> T:
|
|
49
|
+
return TypeAdapter(type_).validate_python(data)
|
|
50
|
+
|
|
51
|
+
def type_validate_json(type_: Type[T], data: Any) -> T:
|
|
52
|
+
return TypeAdapter(type_).validate_json(data)
|
|
53
|
+
|
|
54
|
+
def model_dump(model: BaseModel, by_alias: bool = True) -> Dict[str, Any]:
|
|
55
|
+
return model.model_dump(by_alias=by_alias)
|
|
56
|
+
|
|
57
|
+
def model_rebuild(model: BaseModel):
|
|
58
|
+
model.model_rebuild()
|
|
59
|
+
|
|
60
|
+
def model_before_validator(func: "ModelBeforeValidator"):
|
|
61
|
+
return model_validator(mode="before")(func)
|
|
62
|
+
|
|
63
|
+
def __get_pydantic_core_schema__(
|
|
64
|
+
cls: Type["CustomValidationClass"],
|
|
65
|
+
source_type: Any,
|
|
66
|
+
handler: GetCoreSchemaHandler,
|
|
67
|
+
) -> CoreSchema:
|
|
68
|
+
validators = list(cls.__get_validators__())
|
|
69
|
+
if len(validators) == 1:
|
|
70
|
+
return core_schema.no_info_plain_validator_function(validators[0])
|
|
71
|
+
return core_schema.chain_schema(
|
|
72
|
+
[core_schema.no_info_plain_validator_function(func) for func in validators]
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def custom_validation(class_: Type["CVC"]) -> Type["CVC"]:
|
|
76
|
+
setattr(
|
|
77
|
+
class_,
|
|
78
|
+
"__get_pydantic_core_schema__",
|
|
79
|
+
classmethod(__get_pydantic_core_schema__),
|
|
80
|
+
)
|
|
81
|
+
return class_
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
from pydantic import Extra, BaseModel, parse_obj_as, parse_raw_as, root_validator
|
|
85
|
+
|
|
86
|
+
class GitHubModel(BaseModel):
|
|
87
|
+
class Config:
|
|
88
|
+
allow_population_by_field_name = True
|
|
89
|
+
|
|
90
|
+
class ExtraGitHubModel(BaseModel):
|
|
91
|
+
class Config:
|
|
92
|
+
extra = Extra.allow
|
|
93
|
+
|
|
94
|
+
def type_validate_python(type_: Type[T], data: Any) -> T:
|
|
95
|
+
return parse_obj_as(type_, data)
|
|
96
|
+
|
|
97
|
+
def type_validate_json(type_: Type[T], data: Any) -> T:
|
|
98
|
+
return parse_raw_as(type_, data)
|
|
99
|
+
|
|
100
|
+
def model_dump(model: BaseModel, by_alias: bool = True) -> Dict[str, Any]:
|
|
101
|
+
return model.dict(by_alias=by_alias)
|
|
102
|
+
|
|
103
|
+
def model_rebuild(model: BaseModel):
|
|
104
|
+
return model.update_forward_refs()
|
|
105
|
+
|
|
106
|
+
def model_before_validator(func: "ModelBeforeValidator"):
|
|
107
|
+
return root_validator(pre=True)(func)
|
|
108
|
+
|
|
109
|
+
def custom_validation(class_: Type["CVC"]) -> Type["CVC"]:
|
|
110
|
+
return class_
|
githubkit/config.py
CHANGED
|
@@ -12,6 +12,7 @@ class Config:
|
|
|
12
12
|
user_agent: str
|
|
13
13
|
follow_redirects: bool
|
|
14
14
|
timeout: httpx.Timeout
|
|
15
|
+
http_cache: bool
|
|
15
16
|
|
|
16
17
|
def dict(self) -> Dict[str, Any]:
|
|
17
18
|
return asdict(self)
|
|
@@ -22,7 +23,11 @@ class Config:
|
|
|
22
23
|
|
|
23
24
|
def build_base_url(base_url: Optional[Union[str, httpx.URL]]) -> httpx.URL:
|
|
24
25
|
base_url = base_url or httpx.URL("https://api.github.com/")
|
|
25
|
-
|
|
26
|
+
base_url = base_url if isinstance(base_url, httpx.URL) else httpx.URL(base_url)
|
|
27
|
+
# enforce trailing slash
|
|
28
|
+
if not base_url.raw_path.endswith(b"/"):
|
|
29
|
+
base_url = base_url.copy_with(raw_path=base_url.raw_path + b"/")
|
|
30
|
+
return base_url
|
|
26
31
|
|
|
27
32
|
|
|
28
33
|
def build_accept(
|
|
@@ -61,6 +66,7 @@ def get_config(
|
|
|
61
66
|
user_agent: Optional[str] = None,
|
|
62
67
|
follow_redirects: bool = True,
|
|
63
68
|
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
|
69
|
+
http_cache: bool = True,
|
|
64
70
|
) -> Config:
|
|
65
71
|
return Config(
|
|
66
72
|
build_base_url(base_url),
|
|
@@ -68,4 +74,5 @@ def get_config(
|
|
|
68
74
|
build_user_agent(user_agent),
|
|
69
75
|
follow_redirects,
|
|
70
76
|
build_timeout(timeout),
|
|
77
|
+
http_cache,
|
|
71
78
|
)
|
githubkit/core.py
CHANGED
|
@@ -17,6 +17,7 @@ from typing import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
import httpx
|
|
20
|
+
import hishel
|
|
20
21
|
|
|
21
22
|
from .response import Response
|
|
22
23
|
from .utils import obj_to_jsonable
|
|
@@ -79,6 +80,7 @@ class GitHubCore(Generic[A]):
|
|
|
79
80
|
user_agent: Optional[str] = None,
|
|
80
81
|
follow_redirects: bool = True,
|
|
81
82
|
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
|
83
|
+
http_cache: bool = True,
|
|
82
84
|
):
|
|
83
85
|
...
|
|
84
86
|
|
|
@@ -94,6 +96,7 @@ class GitHubCore(Generic[A]):
|
|
|
94
96
|
user_agent: Optional[str] = None,
|
|
95
97
|
follow_redirects: bool = True,
|
|
96
98
|
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
|
99
|
+
http_cache: bool = True,
|
|
97
100
|
):
|
|
98
101
|
...
|
|
99
102
|
|
|
@@ -109,6 +112,7 @@ class GitHubCore(Generic[A]):
|
|
|
109
112
|
user_agent: Optional[str] = None,
|
|
110
113
|
follow_redirects: bool = True,
|
|
111
114
|
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
|
115
|
+
http_cache: bool = True,
|
|
112
116
|
):
|
|
113
117
|
...
|
|
114
118
|
|
|
@@ -123,6 +127,7 @@ class GitHubCore(Generic[A]):
|
|
|
123
127
|
user_agent: Optional[str] = None,
|
|
124
128
|
follow_redirects: bool = True,
|
|
125
129
|
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
|
130
|
+
http_cache: bool = True,
|
|
126
131
|
):
|
|
127
132
|
auth = auth or UnauthAuthStrategy() # type: ignore
|
|
128
133
|
self.auth: A = ( # type: ignore
|
|
@@ -130,7 +135,13 @@ class GitHubCore(Generic[A]):
|
|
|
130
135
|
)
|
|
131
136
|
|
|
132
137
|
self.config = config or get_config(
|
|
133
|
-
base_url,
|
|
138
|
+
base_url,
|
|
139
|
+
accept_format,
|
|
140
|
+
previews,
|
|
141
|
+
user_agent,
|
|
142
|
+
follow_redirects,
|
|
143
|
+
timeout,
|
|
144
|
+
http_cache,
|
|
134
145
|
)
|
|
135
146
|
|
|
136
147
|
self.__sync_client: ContextVar[Optional[httpx.Client]] = ContextVar(
|
|
@@ -187,7 +198,14 @@ class GitHubCore(Generic[A]):
|
|
|
187
198
|
|
|
188
199
|
# create sync client
|
|
189
200
|
def _create_sync_client(self) -> httpx.Client:
|
|
190
|
-
|
|
201
|
+
if self.config.http_cache:
|
|
202
|
+
transport = hishel.CacheTransport(
|
|
203
|
+
httpx.HTTPTransport(), storage=hishel.InMemoryStorage()
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
transport = httpx.HTTPTransport()
|
|
207
|
+
|
|
208
|
+
return httpx.Client(**self._get_client_defaults(), transport=transport)
|
|
191
209
|
|
|
192
210
|
# get or create sync client
|
|
193
211
|
@contextmanager
|
|
@@ -203,7 +221,14 @@ class GitHubCore(Generic[A]):
|
|
|
203
221
|
|
|
204
222
|
# create async client
|
|
205
223
|
def _create_async_client(self) -> httpx.AsyncClient:
|
|
206
|
-
|
|
224
|
+
if self.config.http_cache:
|
|
225
|
+
transport = hishel.AsyncCacheTransport(
|
|
226
|
+
httpx.AsyncHTTPTransport(), storage=hishel.AsyncInMemoryStorage()
|
|
227
|
+
)
|
|
228
|
+
else:
|
|
229
|
+
transport = httpx.AsyncHTTPTransport()
|
|
230
|
+
|
|
231
|
+
return httpx.AsyncClient(**self._get_client_defaults(), transport=transport)
|
|
207
232
|
|
|
208
233
|
# get or create async client
|
|
209
234
|
@asynccontextmanager
|
githubkit/github.py
CHANGED
|
@@ -15,9 +15,9 @@ from typing import (
|
|
|
15
15
|
|
|
16
16
|
from .core import GitHubCore
|
|
17
17
|
from .response import Response
|
|
18
|
-
from .rest import RestNamespace
|
|
19
18
|
from .paginator import Paginator
|
|
20
19
|
from .auth import BaseAuthStrategy
|
|
20
|
+
from .versions import RestVersionSwitcher, WebhooksVersionSwitcher
|
|
21
21
|
from .graphql import GraphQLResponse, build_graphql_request, parse_graphql_response
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
@@ -84,6 +84,7 @@ class GitHub(GitHubCore[A]):
|
|
|
84
84
|
user_agent: Optional[str] = None,
|
|
85
85
|
follow_redirects: bool = True,
|
|
86
86
|
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
|
87
|
+
http_cache: bool = True,
|
|
87
88
|
):
|
|
88
89
|
...
|
|
89
90
|
|
|
@@ -99,6 +100,7 @@ class GitHub(GitHubCore[A]):
|
|
|
99
100
|
user_agent: Optional[str] = None,
|
|
100
101
|
follow_redirects: bool = True,
|
|
101
102
|
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
|
103
|
+
http_cache: bool = True,
|
|
102
104
|
):
|
|
103
105
|
...
|
|
104
106
|
|
|
@@ -114,6 +116,7 @@ class GitHub(GitHubCore[A]):
|
|
|
114
116
|
user_agent: Optional[str] = None,
|
|
115
117
|
follow_redirects: bool = True,
|
|
116
118
|
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
|
119
|
+
http_cache: bool = True,
|
|
117
120
|
):
|
|
118
121
|
...
|
|
119
122
|
|
|
@@ -126,8 +129,11 @@ class GitHub(GitHubCore[A]):
|
|
|
126
129
|
|
|
127
130
|
# rest api
|
|
128
131
|
@cached_property
|
|
129
|
-
def rest(self) ->
|
|
130
|
-
return
|
|
132
|
+
def rest(self) -> RestVersionSwitcher:
|
|
133
|
+
return RestVersionSwitcher(self)
|
|
134
|
+
|
|
135
|
+
# webhooks
|
|
136
|
+
webhooks = WebhooksVersionSwitcher()
|
|
131
137
|
|
|
132
138
|
# graphql
|
|
133
139
|
def graphql(
|
githubkit/graphql/models.py
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
from typing import Any, Dict, List, Union, Optional
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from githubkit.compat import GitHubModel, model_before_validator
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class SourceLocation(
|
|
6
|
+
class SourceLocation(GitHubModel):
|
|
7
7
|
line: int
|
|
8
8
|
column: int
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class GraphQLError(
|
|
11
|
+
class GraphQLError(GitHubModel):
|
|
12
12
|
message: str
|
|
13
13
|
locations: Optional[List[SourceLocation]] = None
|
|
14
14
|
path: Optional[List[Union[int, str]]] = None
|
|
15
15
|
extensions: Optional[Dict[str, Any]] = None
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
class GraphQLResponse(
|
|
18
|
+
class GraphQLResponse(GitHubModel):
|
|
19
19
|
data: Optional[Dict[str, Any]] = None
|
|
20
20
|
errors: Optional[List[GraphQLError]] = None
|
|
21
21
|
extensions: Optional[Dict[str, Any]] = None
|
|
22
22
|
|
|
23
|
-
@
|
|
23
|
+
@model_before_validator
|
|
24
24
|
@classmethod
|
|
25
|
-
def
|
|
25
|
+
def _validate_data_and_errors(cls, values: Dict[str, Any]):
|
|
26
26
|
if values.get("data") is None and not values.get("errors"):
|
|
27
27
|
raise ValueError("No data or errors found in response")
|
|
28
28
|
return values
|
githubkit/lazy_module.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
import importlib
|
|
4
|
+
from itertools import chain
|
|
5
|
+
from types import ModuleType
|
|
6
|
+
from importlib.abc import MetaPathFinder
|
|
7
|
+
from typing import Any, Dict, List, Tuple, Optional, Sequence
|
|
8
|
+
from importlib.machinery import ModuleSpec, PathFinder, SourceFileLoader
|
|
9
|
+
|
|
10
|
+
LAZY_MODULES = (
|
|
11
|
+
r"^githubkit\.rest$",
|
|
12
|
+
r"^githubkit\.versions\.v[^.]+\.webhooks$",
|
|
13
|
+
r"^githubkit\.versions\.latest\.models$",
|
|
14
|
+
r"^githubkit\.versions\.latest\.types$",
|
|
15
|
+
r"^githubkit\.versions\.latest\.webhooks$",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LazyModule(ModuleType):
|
|
20
|
+
__lazy_vars__: Dict[str, List[str]]
|
|
21
|
+
__lazy_vars_validated__: Optional[Dict[str, List[str]]]
|
|
22
|
+
__lazy_vars_mapping__: Dict[str, str]
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def __all__(self) -> Tuple[str, ...]:
|
|
26
|
+
lazy_vars = self.__lazy_vars_validated__
|
|
27
|
+
if lazy_vars is None:
|
|
28
|
+
return ()
|
|
29
|
+
return tuple(chain.from_iterable(lazy_vars.values()))
|
|
30
|
+
|
|
31
|
+
def __dir__(self):
|
|
32
|
+
result = list(super().__dir__())
|
|
33
|
+
for attr in self.__all__:
|
|
34
|
+
if attr not in result:
|
|
35
|
+
result.append(attr)
|
|
36
|
+
return result
|
|
37
|
+
|
|
38
|
+
def __getattr__(self, name: str) -> Any:
|
|
39
|
+
lazy_vars = self.__lazy_vars_validated__
|
|
40
|
+
# module may not initialized or not valid
|
|
41
|
+
if lazy_vars is None:
|
|
42
|
+
raise AttributeError(f"module '{self.__name__}' has no attribute '{name}'")
|
|
43
|
+
|
|
44
|
+
# check if the attribute is a lazy variable
|
|
45
|
+
if name in self.__lazy_vars_mapping__:
|
|
46
|
+
module = self._get_module(self.__lazy_vars_mapping__[name])
|
|
47
|
+
value = getattr(module, name)
|
|
48
|
+
else:
|
|
49
|
+
raise AttributeError(f"module '{self.__name__}' has no attribute '{name}'")
|
|
50
|
+
|
|
51
|
+
# cache the value
|
|
52
|
+
setattr(self, name, value)
|
|
53
|
+
return value
|
|
54
|
+
|
|
55
|
+
def _get_module(self, module_name: str) -> ModuleType:
|
|
56
|
+
try:
|
|
57
|
+
return importlib.import_module(module_name, self.__name__)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
raise RuntimeError(
|
|
60
|
+
f"Failed to import {module_name} from {self.__name__}"
|
|
61
|
+
) from e
|
|
62
|
+
|
|
63
|
+
def __reduce__(self):
|
|
64
|
+
return (
|
|
65
|
+
self.__class__,
|
|
66
|
+
(self.__name__, self.__file__, getattr(self, "__lazy_vars__", None)),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class LazyModuleLoader(SourceFileLoader):
|
|
71
|
+
def create_module(self, spec: ModuleSpec) -> Optional[ModuleType]:
|
|
72
|
+
if self.name in sys.modules:
|
|
73
|
+
return sys.modules[self.name]
|
|
74
|
+
module = LazyModule(spec.name)
|
|
75
|
+
# pre-initialize the module to avoid infinite recursion
|
|
76
|
+
module.__lazy_vars_validated__ = None
|
|
77
|
+
module.__lazy_vars_mapping__ = {}
|
|
78
|
+
return module
|
|
79
|
+
|
|
80
|
+
def exec_module(self, module: LazyModule) -> None:
|
|
81
|
+
super().exec_module(module)
|
|
82
|
+
|
|
83
|
+
if getattr(module, "__lazy_vars_validated__", None) is None:
|
|
84
|
+
structure = getattr(module, "__lazy_vars__", None)
|
|
85
|
+
if isinstance(structure, dict) and all(
|
|
86
|
+
isinstance(key, str) and isinstance(value, (list, tuple, set))
|
|
87
|
+
for key, value in structure.items()
|
|
88
|
+
):
|
|
89
|
+
module.__lazy_vars_validated__ = structure
|
|
90
|
+
module.__lazy_vars_mapping__ = {
|
|
91
|
+
var: module
|
|
92
|
+
for module, vars in module.__lazy_vars_validated__.items()
|
|
93
|
+
for var in vars
|
|
94
|
+
}
|
|
95
|
+
else:
|
|
96
|
+
raise RuntimeError(
|
|
97
|
+
f"Invalid lazy module structure for {module.__name__}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class LazyModuleFinder(MetaPathFinder):
|
|
102
|
+
def find_spec(
|
|
103
|
+
self,
|
|
104
|
+
fullname: str,
|
|
105
|
+
path: Optional[Sequence[str]],
|
|
106
|
+
target: Optional[ModuleType] = None,
|
|
107
|
+
) -> Optional[ModuleSpec]:
|
|
108
|
+
module_spec = PathFinder.find_spec(fullname, path, target)
|
|
109
|
+
if not module_spec or not module_spec.origin:
|
|
110
|
+
return module_spec
|
|
111
|
+
|
|
112
|
+
if module_spec and any(
|
|
113
|
+
re.match(pattern, module_spec.name) for pattern in LAZY_MODULES
|
|
114
|
+
):
|
|
115
|
+
module_spec.loader = LazyModuleLoader(module_spec.name, module_spec.origin)
|
|
116
|
+
return module_spec
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def apply():
|
|
120
|
+
sys.meta_path.insert(0, LazyModuleFinder())
|
githubkit/response.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from typing import Any, Type, Generic, TypeVar
|
|
2
2
|
|
|
3
3
|
import httpx
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
from .compat import type_validate_json
|
|
5
6
|
|
|
6
7
|
RT = TypeVar("RT")
|
|
7
8
|
|
|
@@ -47,4 +48,4 @@ class Response(Generic[RT]):
|
|
|
47
48
|
|
|
48
49
|
@property
|
|
49
50
|
def parsed_data(self) -> RT:
|
|
50
|
-
return
|
|
51
|
+
return type_validate_json(self._data_model, self.content)
|