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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-source-github
3
- Version: 2.1.41
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.12.0,<8.0.0)
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=ChkWEmqZL7Qrr1zidOfjZWlMWIVRVO2yqZ7QE_HaZP8,2287
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=t3jD8TwnQlbYXed1l_OJvQ3Z98k1e4XOvKQ5ZqA0WIA,11168
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=4nIg4j3n6D_jokf_2Crgcy9wezTZ6PKC7VMD16ejiME,100
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=mYjAHEAXeL8d6Uo0yNgOI5fEqZqDr958l1pSiUG0qIk,18062
59
- source_github/spec.json,sha256=zzvSRPLbLNTNxGvygTSUb1ZuTIhLcFPGg3vowPCZLSc,8243
60
- source_github/streams.py,sha256=_RPdavX-AC9MVdzd6_UqXnt7PV3POovJ25DcUuy3w4U,88216
61
- source_github/utils.py,sha256=aUaJjBoM0xzkpV_Ajbh02p5O-gWQU83MI5_vRIJZL2o,11602
62
- airbyte_source_github-2.1.41.dist-info/METADATA,sha256=KZTIKYHKeBDT2XC8LG_94Dj0P7ElozoX5HtGNsc9YzQ,1249
63
- airbyte_source_github-2.1.41.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
64
- airbyte_source_github-2.1.41.dist-info/entry_points.txt,sha256=gYhqVrTAZvMwuYByg0b_-o115yUFLLcfNxMrLZmiW9k,55
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
- backoff_time_in_seconds = max(float(retry_after), min_backoff_time)
30
- return self.get_waiting_time(backoff_time_in_seconds)
31
-
32
- reset_time = response_or_exception.headers.get("X-RateLimit-Reset")
33
- if reset_time:
34
- backoff_time_in_seconds = max(float(reset_time) - time.time(), min_backoff_time)
35
- return self.get_waiting_time(backoff_time_in_seconds)
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(
@@ -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
- class GithubStreamABCErrorHandler(HttpStatusErrorHandler):
88
- def __init__(self, stream: HttpStream, **kwargs): # type: ignore # noqa
89
- self.stream = stream
90
- super().__init__(**kwargs)
91
-
92
- def _safe_json_check_graphql_rate_limited(self, response: requests.Response) -> bool:
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
- self._logger.warning(
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
- return False
102
- return self.stream.check_graphql_rate_limited(body or {})
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
- # The GitHub GraphQL API has limitations
108
- # https://docs.github.com/en/graphql/overview/resource-limitations
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 = [