airbyte-source-github 2.1.24__tar.gz → 2.1.26__tar.gz

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 (63) hide show
  1. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/PKG-INFO +1 -1
  2. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/pyproject.toml +1 -1
  3. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/errors_handlers.py +39 -5
  4. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/source.py +2 -2
  5. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/streams.py +30 -18
  6. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/README.md +0 -0
  7. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/__init__.py +0 -0
  8. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/backoff_strategies.py +0 -0
  9. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/config_migrations.py +0 -0
  10. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/constants.py +0 -0
  11. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/github_schema.py +0 -0
  12. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/graphql.py +0 -0
  13. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/run.py +0 -0
  14. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/assignees.json +0 -0
  15. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/branches.json +0 -0
  16. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/collaborators.json +0 -0
  17. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/comments.json +0 -0
  18. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/commit_comment_reactions.json +0 -0
  19. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/commit_comments.json +0 -0
  20. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/commits.json +0 -0
  21. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/contributor_activity.json +0 -0
  22. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/deployments.json +0 -0
  23. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/events.json +0 -0
  24. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/issue_comment_reactions.json +0 -0
  25. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/issue_events.json +0 -0
  26. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/issue_labels.json +0 -0
  27. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/issue_milestones.json +0 -0
  28. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/issue_reactions.json +0 -0
  29. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/issue_timeline_events.json +0 -0
  30. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/issues.json +0 -0
  31. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/organizations.json +0 -0
  32. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/project_cards.json +0 -0
  33. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/project_columns.json +0 -0
  34. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/projects.json +0 -0
  35. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/projects_v2.json +0 -0
  36. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/pull_request_comment_reactions.json +0 -0
  37. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/pull_request_commits.json +0 -0
  38. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/pull_request_stats.json +0 -0
  39. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/pull_requests.json +0 -0
  40. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/releases.json +0 -0
  41. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/repositories.json +0 -0
  42. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/review_comments.json +0 -0
  43. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/reviews.json +0 -0
  44. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/shared/events/comment.json +0 -0
  45. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/shared/events/commented.json +0 -0
  46. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/shared/events/committed.json +0 -0
  47. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/shared/events/cross_referenced.json +0 -0
  48. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/shared/events/reviewed.json +0 -0
  49. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/shared/reaction.json +0 -0
  50. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/shared/reactions.json +0 -0
  51. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/shared/user.json +0 -0
  52. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/shared/user_graphql.json +0 -0
  53. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/stargazers.json +0 -0
  54. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/tags.json +0 -0
  55. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/team_members.json +0 -0
  56. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/team_memberships.json +0 -0
  57. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/teams.json +0 -0
  58. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/users.json +0 -0
  59. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/workflow_jobs.json +0 -0
  60. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/workflow_runs.json +0 -0
  61. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/schemas/workflows.json +0 -0
  62. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/spec.json +0 -0
  63. {airbyte_source_github-2.1.24 → airbyte_source_github-2.1.26}/source_github/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airbyte-source-github
3
- Version: 2.1.24
3
+ Version: 2.1.26
4
4
  Summary: Source implementation for GitHub.
5
5
  Home-page: https://airbyte.com
6
6
  License: ELv2
@@ -3,7 +3,7 @@ requires = [ "poetry-core>=1.0.0",]
3
3
  build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
- version = "2.1.24"
6
+ version = "2.1.26"
7
7
  name = "airbyte-source-github"
8
8
  description = "Source implementation for GitHub."
9
9
  authors = [ "Airbyte <contact@airbyte.io>",]
@@ -23,7 +23,12 @@ GITHUB_DEFAULT_ERROR_MAPPING = DEFAULT_ERROR_MAPPING | {
23
23
  403: ErrorResolution(
24
24
  response_action=ResponseAction.FAIL,
25
25
  failure_type=FailureType.config_error,
26
- error_message="Access denied due to insufficient permissions.",
26
+ error_message=(
27
+ "GitHub denied access (HTTP 403). Your token may be missing required scopes "
28
+ "(this connector typically needs: repo, read:org, read:user, read:project, workflow), "
29
+ "or this organization may require SAML SSO authorization. "
30
+ "See https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api"
31
+ ),
27
32
  ),
28
33
  404: ErrorResolution(
29
34
  response_action=ResponseAction.RETRY,
@@ -36,9 +41,13 @@ GITHUB_DEFAULT_ERROR_MAPPING = DEFAULT_ERROR_MAPPING | {
36
41
  error_message="Conflict.",
37
42
  ),
38
43
  410: ErrorResolution(
39
- response_action=ResponseAction.RETRY,
44
+ response_action=ResponseAction.FAIL,
40
45
  failure_type=FailureType.config_error,
41
- error_message="Gone. Please ensure the url is valid.",
46
+ error_message=(
47
+ "GitHub returned 410 Gone for an unexpected reason. "
48
+ "The endpoint or API version may be deprecated. "
49
+ "Verify the connector version is current and the endpoint is still supported."
50
+ ),
42
51
  ),
43
52
  }
44
53
 
@@ -50,6 +59,16 @@ def is_conflict_with_empty_repository(response_or_exception: Optional[Union[requ
50
59
  return False
51
60
 
52
61
 
62
+ def is_gone_with_feature_disabled(response_or_exception: Optional[Union[requests.Response, Exception]] = None) -> bool:
63
+ if isinstance(response_or_exception, requests.Response) and response_or_exception.status_code == requests.codes.GONE:
64
+ try:
65
+ message = (response_or_exception.json().get("message") or "").lower()
66
+ except ValueError:
67
+ return False
68
+ return "are disabled" in message or "is disabled" in message
69
+ return False
70
+
71
+
53
72
  class GithubStreamABCErrorHandler(HttpStatusErrorHandler):
54
73
  def __init__(self, stream: HttpStream, **kwargs): # type: ignore # noqa
55
74
  self.stream = stream
@@ -92,11 +111,26 @@ class GithubStreamABCErrorHandler(HttpStatusErrorHandler):
92
111
  return ErrorResolution(
93
112
  response_action=ResponseAction.RATE_LIMITED,
94
113
  failure_type=FailureType.transient_error,
95
- error_message=f"Response status code: {response_or_exception.status_code}. Retrying...",
114
+ error_message=(
115
+ f"GitHub rate limit hit for stream `{self.stream.name}` "
116
+ f"(HTTP {response_or_exception.status_code}). "
117
+ f"Waiting for the rate limit window to reset before retrying."
118
+ ),
96
119
  )
97
120
 
98
121
  if is_conflict_with_empty_repository(response_or_exception=response_or_exception):
99
- log_message = f"Ignoring response for '{response_or_exception.request.method}' request to '{response_or_exception.url}' with response code '{response_or_exception.status_code}' as the repository is empty."
122
+ log_message = (
123
+ f"Skipping `{self.stream.name}` for this repository: GitHub returned 409 Conflict "
124
+ f"with message 'Git Repository is empty.' This means the repository has no commits."
125
+ )
126
+ return ErrorResolution(
127
+ response_action=ResponseAction.IGNORE,
128
+ failure_type=FailureType.config_error,
129
+ error_message=log_message,
130
+ )
131
+
132
+ if is_gone_with_feature_disabled(response_or_exception=response_or_exception):
133
+ log_message = f"Skipping stream slice for '{response_or_exception.url}': {response_or_exception.json().get('message', 'Feature disabled')}."
100
134
  return ErrorResolution(
101
135
  response_action=ResponseAction.IGNORE,
102
136
  failure_type=FailureType.config_error,
@@ -185,9 +185,9 @@ class SourceGithub(AbstractSource):
185
185
  org_name = message.split("https://api.github.com/orgs/")[1].split("/")[0]
186
186
  user_message = f'Organization name: "{org_name}" is unknown, "repository" config option should be updated. Please validate your repository config.'
187
187
  elif "401 Client Error: Unauthorized for url" in message or ("Error: Unauthorized" in message and "401" in message):
188
- # 401 Client Error: Unauthorized for url: https://api.github.com/orgs/datarootsio/repos?per_page=100&sort=updated&direction=desc
189
188
  user_message = (
190
- "Github credentials have expired or changed, please review your credentials and re-authenticate or renew your access token."
189
+ "GitHub authentication failed (HTTP 401). Please verify your Personal Access Token or OAuth credentials "
190
+ "are valid and not expired."
191
191
  )
192
192
  return user_message
193
193
 
@@ -34,6 +34,7 @@ from .errors_handlers import (
34
34
  GitHubGraphQLErrorHandler,
35
35
  GithubStreamABCErrorHandler,
36
36
  is_conflict_with_empty_repository,
37
+ is_gone_with_feature_disabled,
37
38
  )
38
39
  from .graphql import (
39
40
  CursorStorage,
@@ -151,44 +152,56 @@ class GithubStreamABC(HttpStream, ABC):
151
152
  elif isinstance(self, TeamMemberships):
152
153
  error_msg = f"Syncing `{self.__class__.__name__}` stream for organization `{organisation}`, team `{stream_slice.get('team_slug')}` and user `{stream_slice.get('username')}` isn't available: User has no team membership. Skipping..."
153
154
  else:
154
- error_msg = f"Syncing `{self.__class__.__name__}` stream isn't available for repository `{repository}`."
155
+ error_msg = (
156
+ f"Skipping `{self.__class__.__name__}` for repository `{repository}`: "
157
+ f"GitHub returned 404 Not Found. The repository may not exist, may have been deleted, "
158
+ f"or the configured token may lack access to it."
159
+ )
155
160
  elif e._exception.response.status_code == requests.codes.FORBIDDEN:
156
- error_msg = str(e._exception.response.json().get("message"))
161
+ api_message = (e._exception.response.json() or {}).get("message", "")
157
162
  # When using the `check_connection` method, we should raise an error if we do not have access to the repository.
158
163
  if isinstance(self, Repositories):
159
164
  raise e
160
165
  # When `403` for the stream, that has no access to the organization's teams, based on OAuth Apps Restrictions:
161
166
  # https://docs.github.com/en/organizations/restricting-access-to-your-organizations-data/enabling-oauth-app-access-restrictions-for-your-organization
162
167
  # For all `Organisation` based streams
163
- elif isinstance(self, Organizations) or isinstance(self, Teams) or isinstance(self, Users):
168
+ elif isinstance(self, (Organizations, Teams, Users)):
164
169
  error_msg = (
165
- f"Syncing `{self.name}` stream isn't available for organization `{organisation}`. Full error message: {error_msg}"
170
+ f"Skipping `{self.name}` for organization `{organisation}`: "
171
+ f"GitHub denied access (HTTP 403). Your token may be missing the `read:org` scope, "
172
+ f"or this organization may require SAML SSO authorization. "
173
+ f"GitHub message: {api_message!r}"
166
174
  )
167
175
  # For all other `Repository` base streams
168
176
  else:
169
177
  error_msg = (
170
- f"Syncing `{self.name}` stream isn't available for repository `{repository}`. Full error message: {error_msg}"
178
+ f"Skipping `{self.name}` for repository `{repository}`: "
179
+ f"GitHub denied access (HTTP 403). Your token may be missing required scopes, "
180
+ f"or this organization may require SAML SSO authorization. "
181
+ f"GitHub message: {api_message!r}"
171
182
  )
172
183
  elif e._exception.response.status_code == requests.codes.UNAUTHORIZED:
184
+ api_message = (e._exception.response.json() or {}).get("message", "")
173
185
  if self.access_token_type == constants.PERSONAL_ACCESS_TOKEN_TITLE:
174
- error_msg = str(e._exception.response.json().get("message"))
175
- self.logger.error(f"{self.access_token_type} renewal is required: {error_msg}")
186
+ self.logger.error(
187
+ f"GitHub authentication failed (HTTP 401) for stream `{self.name}`. "
188
+ f"Your Personal Access Token may need to be renewed. GitHub message: {api_message!r}"
189
+ )
176
190
  raise e
177
- elif e._exception.response.status_code == requests.codes.GONE and isinstance(self, Projects):
178
- # Some repos don't have projects enabled and we we get "410 Client Error: Gone for
179
- # url: https://api.github.com/repos/xyz/projects?per_page=100" error.
180
- error_msg = f"Syncing `Projects` stream isn't available for repository `{stream_slice['repository']}`."
191
+
181
192
  elif e._exception.response.status_code == requests.codes.CONFLICT:
182
193
  error_msg = (
183
- f"Syncing `{self.name}` stream isn't available for repository "
184
- f"`{stream_slice['repository']}`, it seems like this repository is empty."
194
+ f"Skipping `{self.name}` for repository `{stream_slice['repository']}`: "
195
+ f"GitHub returned 409 Conflict. The repository is likely empty (no commits)."
185
196
  )
186
197
  elif e._exception.response.status_code == requests.codes.SERVER_ERROR and isinstance(self, WorkflowRuns):
187
198
  error_msg = f"Syncing `{self.name}` stream isn't available for repository `{stream_slice['repository']}`."
188
199
  elif e._exception.response.status_code == requests.codes.BAD_GATEWAY:
189
- error_msg = f"Stream {self.name} temporary failed. Try to re-run sync later"
200
+ error_msg = (
201
+ f"GitHub returned HTTP 502 Bad Gateway for stream `{self.name}` after exhausting retries. "
202
+ f"This is usually transient — the next sync attempt should succeed."
203
+ )
190
204
  else:
191
- # most probably here we're facing a 500 server error and a risk to get a non-json response, so lets output response.text
192
205
  self.logger.error(f"Undefined error while reading records: {e._exception.response.text}")
193
206
  raise e
194
207
 
@@ -245,9 +258,8 @@ class GithubStream(GithubStreamABC):
245
258
  stream_slice: Mapping[str, Any] = None,
246
259
  next_page_token: Mapping[str, Any] = None,
247
260
  ) -> Iterable[Mapping]:
248
- if is_conflict_with_empty_repository(response):
249
- # I would expect that this should be handled (skipped) by the error handler, but it seems like
250
- # ignored this error but continue to processing records. This may be fixed in latest CDK versions.
261
+ if is_conflict_with_empty_repository(response) or is_gone_with_feature_disabled(response):
262
+ # The CDK IGNORE action still calls parse_response; guard against non-array error bodies.
251
263
  return
252
264
  yield from super().parse_response(
253
265
  response=response,