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.
Files changed (143) hide show
  1. githubkit/__init__.py +5 -0
  2. githubkit/auth/_url.py +19 -2
  3. githubkit/auth/app.py +35 -16
  4. githubkit/compat.py +110 -0
  5. githubkit/config.py +8 -1
  6. githubkit/core.py +28 -3
  7. githubkit/github.py +9 -3
  8. githubkit/graphql/models.py +6 -6
  9. githubkit/lazy_module.py +120 -0
  10. githubkit/response.py +3 -2
  11. githubkit/rest/__init__.py +14136 -185
  12. githubkit/typing.py +32 -21
  13. githubkit/utils.py +40 -3
  14. githubkit/versions/__init__.py +19 -0
  15. githubkit/versions/latest/models.py +14147 -0
  16. githubkit/versions/latest/types.py +14283 -0
  17. githubkit/versions/latest/webhooks.py +705 -0
  18. githubkit/versions/rest.py +55 -0
  19. githubkit/versions/v2022_11_28/models/__init__.py +95086 -0
  20. githubkit/versions/v2022_11_28/rest/__init__.py +265 -0
  21. githubkit/{rest → versions/v2022_11_28/rest}/actions.py +1457 -831
  22. githubkit/{rest → versions/v2022_11_28/rest}/activity.py +472 -220
  23. githubkit/{rest → versions/v2022_11_28/rest}/apps.py +470 -235
  24. githubkit/{rest → versions/v2022_11_28/rest}/billing.py +47 -18
  25. githubkit/{rest → versions/v2022_11_28/rest}/checks.py +219 -131
  26. githubkit/{rest → versions/v2022_11_28/rest}/classroom.py +86 -38
  27. githubkit/{rest → versions/v2022_11_28/rest}/code_scanning.py +316 -122
  28. githubkit/{rest → versions/v2022_11_28/rest}/codes_of_conduct.py +29 -10
  29. githubkit/{rest → versions/v2022_11_28/rest}/codespaces.py +726 -345
  30. githubkit/{rest → versions/v2022_11_28/rest}/copilot.py +144 -96
  31. githubkit/{rest → versions/v2022_11_28/rest}/dependabot.py +262 -155
  32. githubkit/{rest → versions/v2022_11_28/rest}/dependency_graph.py +63 -34
  33. githubkit/{rest → versions/v2022_11_28/rest}/emojis.py +17 -8
  34. githubkit/{rest → versions/v2022_11_28/rest}/gists.py +257 -131
  35. githubkit/{rest → versions/v2022_11_28/rest}/git.py +194 -114
  36. githubkit/{rest → versions/v2022_11_28/rest}/gitignore.py +25 -10
  37. githubkit/{rest → versions/v2022_11_28/rest}/interactions.py +135 -56
  38. githubkit/{rest → versions/v2022_11_28/rest}/issues.py +913 -414
  39. githubkit/{rest → versions/v2022_11_28/rest}/licenses.py +47 -16
  40. githubkit/{rest → versions/v2022_11_28/rest}/markdown.py +37 -33
  41. githubkit/{rest → versions/v2022_11_28/rest}/meta.py +44 -17
  42. githubkit/{rest → versions/v2022_11_28/rest}/migrations.py +322 -163
  43. githubkit/{rest → versions/v2022_11_28/rest}/oidc.py +34 -19
  44. githubkit/{rest → versions/v2022_11_28/rest}/orgs.py +2453 -703
  45. githubkit/{rest → versions/v2022_11_28/rest}/packages.py +237 -84
  46. githubkit/{rest → versions/v2022_11_28/rest}/projects.py +468 -244
  47. githubkit/{rest → versions/v2022_11_28/rest}/pulls.py +561 -296
  48. githubkit/{rest → versions/v2022_11_28/rest}/rate_limit.py +17 -8
  49. githubkit/{rest → versions/v2022_11_28/rest}/reactions.py +399 -227
  50. githubkit/{rest → versions/v2022_11_28/rest}/repos.py +3297 -1565
  51. githubkit/{rest → versions/v2022_11_28/rest}/search.py +141 -71
  52. githubkit/{rest → versions/v2022_11_28/rest}/secret_scanning.py +164 -64
  53. githubkit/{rest → versions/v2022_11_28/rest}/security_advisories.py +239 -91
  54. githubkit/{rest → versions/v2022_11_28/rest}/teams.py +774 -448
  55. githubkit/{rest → versions/v2022_11_28/rest}/users.py +552 -266
  56. githubkit/versions/v2022_11_28/types/__init__.py +74008 -0
  57. githubkit/versions/v2022_11_28/webhooks/__init__.py +406 -0
  58. githubkit/versions/v2022_11_28/webhooks/_namespace.py +1314 -0
  59. githubkit/versions/v2022_11_28/webhooks/_types.py +395 -0
  60. githubkit/versions/v2022_11_28/webhooks/branch_protection_configuration.py +45 -0
  61. githubkit/versions/v2022_11_28/webhooks/branch_protection_rule.py +48 -0
  62. githubkit/versions/v2022_11_28/webhooks/check_run.py +46 -0
  63. githubkit/versions/v2022_11_28/webhooks/check_suite.py +43 -0
  64. githubkit/versions/v2022_11_28/webhooks/code_scanning_alert.py +57 -0
  65. githubkit/versions/v2022_11_28/webhooks/commit_comment.py +28 -0
  66. githubkit/versions/v2022_11_28/webhooks/create.py +28 -0
  67. githubkit/versions/v2022_11_28/webhooks/custom_property.py +48 -0
  68. githubkit/versions/v2022_11_28/webhooks/custom_property_values.py +33 -0
  69. githubkit/versions/v2022_11_28/webhooks/delete.py +28 -0
  70. githubkit/versions/v2022_11_28/webhooks/dependabot_alert.py +60 -0
  71. githubkit/versions/v2022_11_28/webhooks/deploy_key.py +37 -0
  72. githubkit/versions/v2022_11_28/webhooks/deployment.py +28 -0
  73. githubkit/versions/v2022_11_28/webhooks/deployment_protection_rule.py +33 -0
  74. githubkit/versions/v2022_11_28/webhooks/deployment_review.py +48 -0
  75. githubkit/versions/v2022_11_28/webhooks/deployment_status.py +33 -0
  76. githubkit/versions/v2022_11_28/webhooks/discussion.py +79 -0
  77. githubkit/versions/v2022_11_28/webhooks/discussion_comment.py +48 -0
  78. githubkit/versions/v2022_11_28/webhooks/fork.py +28 -0
  79. githubkit/versions/v2022_11_28/webhooks/github_app_authorization.py +33 -0
  80. githubkit/versions/v2022_11_28/webhooks/gollum.py +28 -0
  81. githubkit/versions/v2022_11_28/webhooks/installation.py +49 -0
  82. githubkit/versions/v2022_11_28/webhooks/installation_repositories.py +45 -0
  83. githubkit/versions/v2022_11_28/webhooks/installation_target.py +33 -0
  84. githubkit/versions/v2022_11_28/webhooks/issue_comment.py +43 -0
  85. githubkit/versions/v2022_11_28/webhooks/issues.py +82 -0
  86. githubkit/versions/v2022_11_28/webhooks/label.py +39 -0
  87. githubkit/versions/v2022_11_28/webhooks/marketplace_purchase.py +54 -0
  88. githubkit/versions/v2022_11_28/webhooks/member.py +39 -0
  89. githubkit/versions/v2022_11_28/webhooks/membership.py +37 -0
  90. githubkit/versions/v2022_11_28/webhooks/merge_group.py +37 -0
  91. githubkit/versions/v2022_11_28/webhooks/meta.py +28 -0
  92. githubkit/versions/v2022_11_28/webhooks/milestone.py +49 -0
  93. githubkit/versions/v2022_11_28/webhooks/org_block.py +37 -0
  94. githubkit/versions/v2022_11_28/webhooks/organization.py +49 -0
  95. githubkit/versions/v2022_11_28/webhooks/package.py +37 -0
  96. githubkit/versions/v2022_11_28/webhooks/page_build.py +28 -0
  97. githubkit/versions/v2022_11_28/webhooks/personal_access_token_request.py +51 -0
  98. githubkit/versions/v2022_11_28/webhooks/ping.py +28 -0
  99. githubkit/versions/v2022_11_28/webhooks/project.py +49 -0
  100. githubkit/versions/v2022_11_28/webhooks/project_card.py +49 -0
  101. githubkit/versions/v2022_11_28/webhooks/project_column.py +46 -0
  102. githubkit/versions/v2022_11_28/webhooks/projects_v2.py +49 -0
  103. githubkit/versions/v2022_11_28/webhooks/projects_v2_item.py +60 -0
  104. githubkit/versions/v2022_11_28/webhooks/public.py +28 -0
  105. githubkit/versions/v2022_11_28/webhooks/pull_request.py +130 -0
  106. githubkit/versions/v2022_11_28/webhooks/pull_request_review.py +48 -0
  107. githubkit/versions/v2022_11_28/webhooks/pull_request_review_comment.py +48 -0
  108. githubkit/versions/v2022_11_28/webhooks/pull_request_review_thread.py +45 -0
  109. githubkit/versions/v2022_11_28/webhooks/push.py +28 -0
  110. githubkit/versions/v2022_11_28/webhooks/registry_package.py +42 -0
  111. githubkit/versions/v2022_11_28/webhooks/release.py +55 -0
  112. githubkit/versions/v2022_11_28/webhooks/repository.py +61 -0
  113. githubkit/versions/v2022_11_28/webhooks/repository_advisory.py +45 -0
  114. githubkit/versions/v2022_11_28/webhooks/repository_dispatch.py +33 -0
  115. githubkit/versions/v2022_11_28/webhooks/repository_import.py +33 -0
  116. githubkit/versions/v2022_11_28/webhooks/repository_ruleset.py +48 -0
  117. githubkit/versions/v2022_11_28/webhooks/repository_vulnerability_alert.py +51 -0
  118. githubkit/versions/v2022_11_28/webhooks/secret_scanning_alert.py +51 -0
  119. githubkit/versions/v2022_11_28/webhooks/secret_scanning_alert_location.py +33 -0
  120. githubkit/versions/v2022_11_28/webhooks/security_advisory.py +48 -0
  121. githubkit/versions/v2022_11_28/webhooks/security_and_analysis.py +33 -0
  122. githubkit/versions/v2022_11_28/webhooks/sponsorship.py +52 -0
  123. githubkit/versions/v2022_11_28/webhooks/star.py +37 -0
  124. githubkit/versions/v2022_11_28/webhooks/status.py +28 -0
  125. githubkit/versions/v2022_11_28/webhooks/team.py +49 -0
  126. githubkit/versions/v2022_11_28/webhooks/team_add.py +28 -0
  127. githubkit/versions/v2022_11_28/webhooks/watch.py +28 -0
  128. githubkit/versions/v2022_11_28/webhooks/workflow_dispatch.py +33 -0
  129. githubkit/versions/v2022_11_28/webhooks/workflow_job.py +46 -0
  130. githubkit/versions/v2022_11_28/webhooks/workflow_run.py +43 -0
  131. githubkit/versions/webhooks.py +50 -0
  132. githubkit/webhooks/__init__.py +21 -8
  133. {githubkit-0.11.0a0.dist-info → githubkit-0.11.0a1.dist-info}/METADATA +119 -17
  134. githubkit-0.11.0a1.dist-info/RECORD +149 -0
  135. {githubkit-0.11.0a0.dist-info → githubkit-0.11.0a1.dist-info}/WHEEL +1 -1
  136. githubkit/rest/models.py +0 -22271
  137. githubkit/rest/types.py +0 -15068
  138. githubkit/webhooks/models.py +0 -18290
  139. githubkit/webhooks/parse.py +0 -27
  140. githubkit/webhooks/types.py +0 -1171
  141. githubkit/webhooks/verify.py +0 -81
  142. githubkit-0.11.0a0.dist-info/RECORD +0 -70
  143. {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
- APP_AUTH_REGEX = re.compile(rf"(?:{'|'.join(APP_ROUTES)})[^/]*$", re.I)
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
- # bypass oauth web flow and device flow
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.rest import (
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.rest.types import AppPermissionsType
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 = AppInstallationsInstallationIdAccessTokensPostBody.model_validate(
95
- {
96
- "repositories": self.repositories,
97
- "repository_ids": self.repository_ids,
98
- "permissions": self.permissions,
99
- }
100
- ).model_dump(by_alias=True)
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
- return base_url if isinstance(base_url, httpx.URL) else httpx.URL(base_url)
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, accept_format, previews, user_agent, follow_redirects, timeout
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
- return httpx.Client(**self._get_client_defaults())
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
- return httpx.AsyncClient(**self._get_client_defaults())
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) -> RestNamespace:
130
- return RestNamespace(self)
132
+ def rest(self) -> RestVersionSwitcher:
133
+ return RestVersionSwitcher(self)
134
+
135
+ # webhooks
136
+ webhooks = WebhooksVersionSwitcher()
131
137
 
132
138
  # graphql
133
139
  def graphql(
@@ -1,28 +1,28 @@
1
1
  from typing import Any, Dict, List, Union, Optional
2
2
 
3
- from pydantic import BaseModel, model_validator
3
+ from githubkit.compat import GitHubModel, model_before_validator
4
4
 
5
5
 
6
- class SourceLocation(BaseModel):
6
+ class SourceLocation(GitHubModel):
7
7
  line: int
8
8
  column: int
9
9
 
10
10
 
11
- class GraphQLError(BaseModel):
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(BaseModel):
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
- @model_validator(mode="before")
23
+ @model_before_validator
24
24
  @classmethod
25
- def validate_data_and_errors(cls, values: Dict[str, Any]):
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
@@ -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
- from pydantic import TypeAdapter
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 TypeAdapter(self._data_model).validate_json(self.content)
51
+ return type_validate_json(self._data_model, self.content)