airbyte-source-github 2.1.41__py3-none-any.whl → 2.2.0__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.
- {airbyte_source_github-2.1.41.dist-info → airbyte_source_github-2.2.0.dist-info}/METADATA +2 -2
- {airbyte_source_github-2.1.41.dist-info → airbyte_source_github-2.2.0.dist-info}/RECORD +10 -12
- source_github/backoff_strategies.py +27 -18
- source_github/errors_handlers.py +30 -23
- source_github/manifest.yaml +1327 -2
- source_github/source.py +149 -95
- source_github/streams.py +16 -42
- source_github/utils.py +1 -250
- source_github/schemas/repositories.json +0 -427
- source_github/spec.json +0 -204
- {airbyte_source_github-2.1.41.dist-info → airbyte_source_github-2.2.0.dist-info}/WHEEL +0 -0
- {airbyte_source_github-2.1.41.dist-info → airbyte_source_github-2.2.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: airbyte-source-github
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Source implementation for GitHub.
|
|
5
5
|
Home-page: https://airbyte.com
|
|
6
6
|
License: ELv2
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Requires-Dist: airbyte-cdk (>=7.
|
|
16
|
+
Requires-Dist: airbyte-cdk (>=7.28.2,<8.0.0)
|
|
17
17
|
Requires-Dist: sgqlc (==16.3)
|
|
18
18
|
Project-URL: Documentation, https://docs.airbyte.com/integrations/sources/github
|
|
19
19
|
Project-URL: Repository, https://github.com/airbytehq/airbyte
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
source_github/__init__.py,sha256=8hLLjFC1wI1mMU442Y5qCcND9qIwyhaANQwT-GAIK0s,1135
|
|
2
|
-
source_github/backoff_strategies.py,sha256=
|
|
2
|
+
source_github/backoff_strategies.py,sha256=H1n3xNkkrlto7MqJY-Yp0el2G5S4yuJ7z3kg6aimab0,2343
|
|
3
3
|
source_github/config_migrations.py,sha256=guUJAdNP-liUciVaJB4ackEEMCN4jd7bNd85QFmDNlU,3932
|
|
4
4
|
source_github/constants.py,sha256=Hj3Q4y7OoU-Iff4m9gEC2CjwmWJYXhNbHVNjg8EBLmQ,238
|
|
5
|
-
source_github/errors_handlers.py,sha256=
|
|
5
|
+
source_github/errors_handlers.py,sha256=y0SjYuxKo3zBRhk2kZe0NZWsKen4W9x-wD84Q0S3sjU,11211
|
|
6
6
|
source_github/github_schema.py,sha256=2AXmTN_s_VqvZAR1GPqus5HhSXWokS4N7HMQQqefaYw,1600315
|
|
7
7
|
source_github/graphql.py,sha256=fGuYzDynYjJOR7u3GX0t6eBJMzdFxKIp4gHP73MCpos,13518
|
|
8
|
-
source_github/manifest.yaml,sha256=
|
|
8
|
+
source_github/manifest.yaml,sha256=D6z6gNZoy8yd7SZ50AxwhEx3Ky6gmsz16B4CL4ILXxM,56068
|
|
9
9
|
source_github/run.py,sha256=gfWy8TcMHa0XRwaVUyq4KmslooST38fvfCLuP5D-5Fo,2391
|
|
10
10
|
source_github/schemas/assignees.json,sha256=_WmGIwYmTmEi9WWkvXdFF3CLsMzG2usoIjaqw-tb4NA,2335
|
|
11
11
|
source_github/schemas/branches.json,sha256=3CIkdEi2XVFoVZrpO_U04mMI7-jSb9i-CT3wbIEgPik,2734
|
|
@@ -34,7 +34,6 @@ source_github/schemas/pull_request_commits.json,sha256=WICcuxUsExJQe1qlXvXVN-Vvk
|
|
|
34
34
|
source_github/schemas/pull_request_stats.json,sha256=harNH5ha6su-2OhxszHOqDKJ34OZzUsL_4nBnaOD16o,3497
|
|
35
35
|
source_github/schemas/pull_requests.json,sha256=Yp9Sq3jgpfBn0YFbcZC_sy06rlJ-AMkCbHNq90KXT_Y,12196
|
|
36
36
|
source_github/schemas/releases.json,sha256=3ITMlMFML3ZSfo_EAkVajJdaS_Xxz_8V4LhL9GRwWrY,5121
|
|
37
|
-
source_github/schemas/repositories.json,sha256=WpC5ZjoycnHUP5DNz8ImEXROYAel8DGRkBN10W1mQ2w,13319
|
|
38
37
|
source_github/schemas/review_comments.json,sha256=HVH3OE-x_wYVvg0Qxn6rzXOwAGEk0y3De8_VLCgE3mA,4905
|
|
39
38
|
source_github/schemas/reviews.json,sha256=OPqo9Dz6slRGojamq8pBMKKRZgz96kDRkYiUJjJQH_E,2731
|
|
40
39
|
source_github/schemas/shared/events/comment.json,sha256=6dcatpi1gitgcR_JscPllyGGoSb8Pgce5OS9vh1HKP4,7400
|
|
@@ -55,11 +54,10 @@ source_github/schemas/users.json,sha256=by1CEx3MW740S5F54KZfUDc2N1_UZ98Bb3fWlgCt
|
|
|
55
54
|
source_github/schemas/workflow_jobs.json,sha256=Kk3N3FcCYBqOdR7FYvz2nAAFtLZBuVfR8Lxg-ORD39U,3934
|
|
56
55
|
source_github/schemas/workflow_runs.json,sha256=XDmIsjtzka-ItEonImD3ZATZjxRNkbFo5-MPTtZDARA,19453
|
|
57
56
|
source_github/schemas/workflows.json,sha256=gSNw8WZaVKbX4AL97PbjZHzvxcOltXqv9Ao1RNQOFXM,1470
|
|
58
|
-
source_github/source.py,sha256=
|
|
59
|
-
source_github/
|
|
60
|
-
source_github/
|
|
61
|
-
|
|
62
|
-
airbyte_source_github-2.
|
|
63
|
-
airbyte_source_github-2.
|
|
64
|
-
airbyte_source_github-2.
|
|
65
|
-
airbyte_source_github-2.1.41.dist-info/RECORD,,
|
|
57
|
+
source_github/source.py,sha256=eqjqxGcR0EfoSgYb6ZHqKUYvwl609_8hMsRWoFOPD4k,22235
|
|
58
|
+
source_github/streams.py,sha256=icnFwlrjGpIrgY-OlOLQqv5gkKcXC5OKxpxTJYLRMxI,86936
|
|
59
|
+
source_github/utils.py,sha256=rkOk8VG6ey1xDl-9t4B99N6HrvTTPCTGhvifrKwYG9Y,706
|
|
60
|
+
airbyte_source_github-2.2.0.dist-info/METADATA,sha256=bDlXZ8k2l0QqzAgYtDrkon94cpabg8diK-tKr5GnvlM,1248
|
|
61
|
+
airbyte_source_github-2.2.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
62
|
+
airbyte_source_github-2.2.0.dist-info/entry_points.txt,sha256=gYhqVrTAZvMwuYByg0b_-o115yUFLLcfNxMrLZmiW9k,55
|
|
63
|
+
airbyte_source_github-2.2.0.dist-info/RECORD,,
|
|
@@ -10,38 +10,47 @@ import requests
|
|
|
10
10
|
from airbyte_cdk import BackoffStrategy
|
|
11
11
|
from airbyte_cdk.sources.streams.http import HttpStream
|
|
12
12
|
|
|
13
|
+
from .errors_handlers import is_rate_limited_response
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
class GithubStreamABCBackoffStrategy(BackoffStrategy):
|
|
15
|
-
def __init__(self, stream: HttpStream, **kwargs): # type: ignore # noqa
|
|
17
|
+
def __init__(self, stream: HttpStream, max_wait_time_seconds: float = 120 * 60, **kwargs): # type: ignore # noqa
|
|
16
18
|
self.stream = stream
|
|
19
|
+
self.max_wait_time_seconds = max_wait_time_seconds
|
|
17
20
|
super().__init__(**kwargs)
|
|
18
21
|
|
|
19
22
|
def backoff_time(
|
|
20
23
|
self, response_or_exception: Optional[Union[requests.Response, requests.RequestException]], **kwargs: Any
|
|
21
24
|
) -> Optional[float]:
|
|
22
|
-
# This method is called if we run into the rate limit. GitHub limits requests to 5000 per hour and provides
|
|
23
|
-
# `X-RateLimit-Reset` header which contains time when this hour will be finished and limits will be reset so
|
|
24
|
-
# we again could have 5000 per another hour.
|
|
25
25
|
if isinstance(response_or_exception, requests.Response):
|
|
26
|
+
if not is_rate_limited_response(
|
|
27
|
+
response_or_exception,
|
|
28
|
+
self.stream.check_graphql_rate_limited,
|
|
29
|
+
self.stream.logger,
|
|
30
|
+
):
|
|
31
|
+
return None
|
|
32
|
+
|
|
26
33
|
min_backoff_time = 60.0
|
|
27
34
|
retry_after = response_or_exception.headers.get("Retry-After")
|
|
28
35
|
if retry_after is not None:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
wait_time = max(float(retry_after), min_backoff_time)
|
|
37
|
+
else:
|
|
38
|
+
reset_time = response_or_exception.headers.get("X-RateLimit-Reset")
|
|
39
|
+
if not reset_time:
|
|
40
|
+
return None
|
|
41
|
+
wait_time = max(float(reset_time) - time.time(), min_backoff_time)
|
|
42
|
+
|
|
43
|
+
if wait_time > self.max_wait_time_seconds:
|
|
44
|
+
self.stream.logger.warning(
|
|
45
|
+
"Rate limit wait for stream `%s` is %.2f seconds, exceeding the maximum of %.2f seconds; using default backoff.",
|
|
46
|
+
self.stream.name,
|
|
47
|
+
wait_time,
|
|
48
|
+
self.max_wait_time_seconds,
|
|
49
|
+
)
|
|
50
|
+
return None
|
|
51
|
+
return wait_time
|
|
36
52
|
return None
|
|
37
53
|
|
|
38
|
-
def get_waiting_time(self, backoff_time_in_seconds: Optional[float]) -> Optional[float]:
|
|
39
|
-
if backoff_time_in_seconds < 60 * 10: # type: ignore[operator]
|
|
40
|
-
return backoff_time_in_seconds
|
|
41
|
-
else:
|
|
42
|
-
self.stream._http_client._session.auth.update_token() # New token will be used in next request
|
|
43
|
-
return 1
|
|
44
|
-
|
|
45
54
|
|
|
46
55
|
class ContributorActivityBackoffStrategy(BackoffStrategy):
|
|
47
56
|
def backoff_time(
|
source_github/errors_handlers.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
-
from typing import Optional, Union
|
|
6
|
+
from typing import Callable, Optional, Union
|
|
7
7
|
|
|
8
8
|
import requests
|
|
9
9
|
|
|
@@ -84,38 +84,45 @@ def is_gone_with_feature_disabled(response_or_exception: Optional[Union[requests
|
|
|
84
84
|
return False
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
def is_rate_limited_response(
|
|
88
|
+
response: requests.Response, graphql_rate_limit_checker: Callable[[dict], bool], logger: logging.Logger
|
|
89
|
+
) -> bool:
|
|
90
|
+
# GitHub GraphQL resource limitations:
|
|
91
|
+
# https://docs.github.com/en/graphql/overview/resource-limitations
|
|
92
|
+
if response.headers.get("X-RateLimit-Resource") == "graphql":
|
|
93
93
|
try:
|
|
94
94
|
body = response.json()
|
|
95
95
|
except ValueError:
|
|
96
|
-
|
|
96
|
+
logger.warning(
|
|
97
97
|
"GraphQL rate-limit check received non-JSON response (HTTP %s, first 50 chars: %r).",
|
|
98
98
|
response.status_code,
|
|
99
99
|
response.text[:50],
|
|
100
100
|
)
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
graphql_rate_limited = False
|
|
102
|
+
else:
|
|
103
|
+
graphql_rate_limited = graphql_rate_limit_checker(body or {})
|
|
104
|
+
|
|
105
|
+
if graphql_rate_limited:
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
# GitHub REST rate-limit HTTP headers:
|
|
109
|
+
# https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limit-http-headers
|
|
110
|
+
# GitHub secondary rate limits:
|
|
111
|
+
# https://docs.github.com/en/rest/overview/resources-in-the-rest-api#secondary-rate-limits
|
|
112
|
+
return (response.status_code != 200 and response.headers.get("X-RateLimit-Remaining") == "0") or "Retry-After" in response.headers
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class GithubStreamABCErrorHandler(HttpStatusErrorHandler):
|
|
116
|
+
def __init__(self, stream: HttpStream, **kwargs): # type: ignore # noqa
|
|
117
|
+
self.stream = stream
|
|
118
|
+
super().__init__(**kwargs)
|
|
103
119
|
|
|
104
120
|
def interpret_response(self, response_or_exception: Optional[Union[requests.Response, Exception]] = None) -> ErrorResolution:
|
|
105
121
|
if isinstance(response_or_exception, requests.Response):
|
|
106
|
-
retry_flag = (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
response_or_exception.headers.get("X-RateLimit-Resource") == "graphql"
|
|
111
|
-
and self._safe_json_check_graphql_rate_limited(response_or_exception)
|
|
112
|
-
)
|
|
113
|
-
# Rate limit HTTP headers
|
|
114
|
-
# https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limit-http-headers
|
|
115
|
-
or (response_or_exception.status_code != 200 and response_or_exception.headers.get("X-RateLimit-Remaining") == "0")
|
|
116
|
-
# Secondary rate limits
|
|
117
|
-
# https://docs.github.com/en/rest/overview/resources-in-the-rest-api#secondary-rate-limits
|
|
118
|
-
or "Retry-After" in response_or_exception.headers
|
|
122
|
+
retry_flag = is_rate_limited_response(
|
|
123
|
+
response_or_exception,
|
|
124
|
+
self.stream.check_graphql_rate_limited,
|
|
125
|
+
self._logger,
|
|
119
126
|
)
|
|
120
127
|
if retry_flag:
|
|
121
128
|
headers = [
|