airbyte-source-github 2.2.1__tar.gz → 2.2.2__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 (62) hide show
  1. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/PKG-INFO +1 -1
  2. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/pyproject.toml +1 -1
  3. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/manifest.yaml +34 -2
  4. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/source.py +13 -8
  5. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/streams.py +24 -3
  6. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/.airbyte-pypi-readme.md +0 -0
  7. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/__init__.py +0 -0
  8. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/backoff_strategies.py +0 -0
  9. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/config_migrations.py +0 -0
  10. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/constants.py +0 -0
  11. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/errors_handlers.py +0 -0
  12. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/github_schema.py +0 -0
  13. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/graphql.py +0 -0
  14. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/run.py +0 -0
  15. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/assignees.json +0 -0
  16. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/branches.json +0 -0
  17. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/collaborators.json +0 -0
  18. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/comments.json +0 -0
  19. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/commit_comment_reactions.json +0 -0
  20. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/commit_comments.json +0 -0
  21. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/commits.json +0 -0
  22. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/contributor_activity.json +0 -0
  23. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/deployments.json +0 -0
  24. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/events.json +0 -0
  25. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/issue_comment_reactions.json +0 -0
  26. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/issue_events.json +0 -0
  27. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/issue_labels.json +0 -0
  28. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/issue_milestones.json +0 -0
  29. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/issue_reactions.json +0 -0
  30. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/issue_timeline_events.json +0 -0
  31. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/issues.json +0 -0
  32. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/organizations.json +0 -0
  33. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/project_cards.json +0 -0
  34. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/project_columns.json +0 -0
  35. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/projects.json +0 -0
  36. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/projects_v2.json +0 -0
  37. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/pull_request_comment_reactions.json +0 -0
  38. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/pull_request_commits.json +0 -0
  39. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/pull_request_stats.json +0 -0
  40. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/pull_requests.json +0 -0
  41. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/releases.json +0 -0
  42. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/review_comments.json +0 -0
  43. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/reviews.json +0 -0
  44. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/shared/events/comment.json +0 -0
  45. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/shared/events/commented.json +0 -0
  46. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/shared/events/committed.json +0 -0
  47. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/shared/events/cross_referenced.json +0 -0
  48. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/shared/events/reviewed.json +0 -0
  49. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/shared/reaction.json +0 -0
  50. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/shared/reactions.json +0 -0
  51. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/shared/user.json +0 -0
  52. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/shared/user_graphql.json +0 -0
  53. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/stargazers.json +0 -0
  54. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/tags.json +0 -0
  55. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/team_members.json +0 -0
  56. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/team_memberships.json +0 -0
  57. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/teams.json +0 -0
  58. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/users.json +0 -0
  59. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/workflow_jobs.json +0 -0
  60. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/workflow_runs.json +0 -0
  61. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/source_github/schemas/workflows.json +0 -0
  62. {airbyte_source_github-2.2.1 → airbyte_source_github-2.2.2}/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.2.1
3
+ Version: 2.2.2
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.2.1"
6
+ version = "2.2.2"
7
7
  name = "airbyte-source-github"
8
8
  description = "Source implementation for GitHub."
9
9
  authors = [ "Airbyte <contact@airbyte.io>",]
@@ -405,8 +405,11 @@ definitions:
405
405
  - string
406
406
 
407
407
  # Shared partition routers. Repo-scoped streams reference
408
- # `repository_partition_router`; org-scoped streams reference
409
- # `organization_partition_router`.
408
+ # `repository_partition_router`. For orgs there are deliberately two routers with
409
+ # different audiences: the `repositories` stream references
410
+ # `organization_partition_router` (config-derived candidates), the org-scoped Python
411
+ # streams reference `organization_resolution_partition_router` (payload-confirmed).
412
+ # Picking the wrong one for an org-scoped stream is what broke 2.2.0 — see below.
410
413
  #
411
414
  # `repository_partition_router` yields the deduplicated union of
412
415
  # wildcard-expanded repos and validated explicit repos.
@@ -415,6 +418,16 @@ definitions:
415
418
  # orgs (derived inline from config) and the orgs owning explicit repos.
416
419
  # User-owned repos have no `organization` object, so the substream child
417
420
  # skips them (they contribute a repository but not an organization).
421
+ # It drives the `repositories` stream, which has to *attempt* `orgs/{org}/repos`
422
+ # for a wildcard entry to learn whether the login is an organization at all.
423
+ #
424
+ # `organization_resolution_partition_router` is the narrower list the org-scoped
425
+ # Python streams slice on, and must not be the router above: a login taken from
426
+ # config text is only a *candidate* org, and `orgs/{login}` 404s on every
427
+ # org-scoped stream when it is a user. Both branches here take the org from a
428
+ # response payload instead — `owner/login` of the repos `orgs/{org}/repos`
429
+ # returned, `organization/login` of `repos/{owner}/{repo}` (absent for user-owned
430
+ # repos) — which is how the legacy Python resolver built the list.
418
431
  repository_partition_router:
419
432
  type: UnionPartitionRouter
420
433
  partition_field: repository
@@ -450,6 +463,25 @@ definitions:
450
463
  parent_key: "organization/login"
451
464
  partition_field: "organization"
452
465
 
466
+ organization_resolution_partition_router:
467
+ type: UnionPartitionRouter
468
+ partition_field: organization
469
+ partition_routers:
470
+ - type: SubstreamPartitionRouter
471
+ parent_stream_configs:
472
+ - type: ParentStreamConfig
473
+ stream:
474
+ $ref: "#/definitions/repositories_resolver_stream"
475
+ parent_key: "owner/login"
476
+ partition_field: "organization"
477
+ - type: SubstreamPartitionRouter
478
+ parent_stream_configs:
479
+ - type: ParentStreamConfig
480
+ stream:
481
+ $ref: "#/definitions/repository_stats_stream"
482
+ parent_key: "organization/login"
483
+ partition_field: "organization"
484
+
453
485
  repositories_stream:
454
486
  type: DeclarativeStream
455
487
  name: repositories
@@ -143,8 +143,8 @@ class SourceGithub(YamlDeclarativeSource, AbstractSource):
143
143
 
144
144
  def _resolve_repositories_and_organizations(self, config: Mapping[str, Any]) -> Tuple[List[str], List[str]]:
145
145
  """Resolve wildcard patterns and explicit repos by enumerating the manifest's
146
- shared partition routers — the same components manifest streams slice on at
147
- read time, so the Python streams are guaranteed to see identical lists.
146
+ partition routers — the same components manifest streams slice on at read
147
+ time, so repo-scoped Python streams see the same repository list.
148
148
 
149
149
  Wildcard patterns (`org/*`, `org/prefix*`) expand via `repositories_resolver`;
150
150
  explicit `org/repo` entries validate via `repository_stats`; entries that 404
@@ -153,6 +153,11 @@ class SourceGithub(YamlDeclarativeSource, AbstractSource):
153
153
  The resolver streams' `use_cache: true` means this enumeration warms the HTTP
154
154
  cache the manifest streams reuse when reading.
155
155
 
156
+ Organizations come from `organization_resolution_partition_router` — payload-
157
+ confirmed logins only — which is deliberately narrower than the
158
+ `organization_partition_router` the `repositories` stream slices on; see that
159
+ definition's comment in the manifest.
160
+
156
161
  Returns (organizations, repositories), both sorted and deduplicated.
157
162
  """
158
163
  try:
@@ -175,15 +180,15 @@ class SourceGithub(YamlDeclarativeSource, AbstractSource):
175
180
  return sorted({stream_slice.partition[partition_key] for stream_slice in router.stream_slices()})
176
181
 
177
182
  repositories = enumerate_router("repository_partition_router", "repository")
178
- # The organization router unions config-derived wildcard orgs with the orgs
179
- # owning explicit repos, so a wildcard entry whose org doesn't exist (404) or
180
- # matched no repos would still yield a partition. Keep only orgs that own at
181
- # least one resolved repository the legacy resolver derived orgs from
182
- # fetched repo metadata, so orgs without any synced repo never surfaced.
183
+ # `organization_resolution_partition_router`, not `organization_partition_router`: a
184
+ # login that config only *claims* is an org must never reach the org-scoped streams.
185
+ # Config-derived orgs put a user login on `Organizations`, `Teams` and `Users`, where
186
+ # `orgs/{user}` 404s on every request. The `in repository_owners` filter is a backstop:
187
+ # an org owning no resolved repository has nothing to sync.
183
188
  repository_owners = {repository.split("/", 1)[0] for repository in repositories}
184
189
  organizations = [
185
190
  organization
186
- for organization in enumerate_router("organization_partition_router", "organization")
191
+ for organization in enumerate_router("organization_resolution_partition_router", "organization")
187
192
  if organization in repository_owners
188
193
  ]
189
194
  return organizations, repositories
@@ -213,11 +213,34 @@ class GithubStreamABC(HttpStream, ABC):
213
213
  raise e
214
214
 
215
215
  self.logger.warning(error_msg)
216
+ self._close_slice_after_swallowed_error(stream_slice)
216
217
  # Exhausting every token no longer raises a connector-specific exception here: the
217
218
  # shared authenticator raises AirbyteTracedException(transient_error) itself, which the
218
219
  # `except AirbyteTracedException` branch above re-raises untouched (it carries no
219
220
  # response to classify).
220
221
 
222
+ def _close_slice_after_swallowed_error(self, stream_slice: Optional[Mapping[str, Any]]) -> None:
223
+ """Mark the slice complete when `read_records` skipped it instead of raising.
224
+
225
+ `HttpStream._read_pages` closes a resumable-full-refresh slice only after the last
226
+ page, so an error swallowed mid-slice leaves the partition's cursor state empty and
227
+ `CursorBasedCheckpointReader._find_next_slice` hands the same partition back forever
228
+ — no record, no STATE, and the platform kills the attempt on the source heartbeat.
229
+ Closing the slice makes a swallowed error terminal, as the warning above implies.
230
+ """
231
+ cursor = self.get_cursor()
232
+ if not isinstance(cursor, SubstreamResumableFullRefreshCursor):
233
+ return
234
+ # Only close a partition the slice actually names. Several substreams read their parent
235
+ # by calling its `read_records` straight from `stream_slices()` with a bare mapping that
236
+ # has no `partition` key (`TeamMembers`, `IssueTimelineEvents`, `utils.read_full_refresh`,
237
+ # ...), and those parents are shared instances that emit their own STATE later — closing
238
+ # `_extract_slice_fields`' `{}` fallback would put a meaningless entry in it.
239
+ partition = (stream_slice or {}).get("partition")
240
+ if not partition:
241
+ return
242
+ cursor.close_slice(StreamSlice(cursor_slice={}, partition=partition))
243
+
221
244
 
222
245
  class GithubStream(GithubStreamABC):
223
246
  def __init__(self, repositories: List[str], page_size_for_large_streams: int, **kwargs):
@@ -1876,9 +1899,7 @@ class ContributorActivity(GithubStream):
1876
1899
  # In order to retain the existing stream behavior before we added RFR to this stream, we need to close out the
1877
1900
  # partition after we give up the maximum number of retries on the 202 response. This does lead to the question
1878
1901
  # of if we should prematurely exit in the first place, but for now we're going to aim for feature parity
1879
- partition_obj = stream_slice.get("partition")
1880
- if self.cursor and partition_obj:
1881
- self.cursor.close_slice(StreamSlice(cursor_slice={}, partition=partition_obj))
1902
+ self._close_slice_after_swallowed_error(stream_slice)
1882
1903
  else:
1883
1904
  raise e
1884
1905
  else: