airbyte-source-github 1.8.41__tar.gz → 1.9.0rc1__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.
Potentially problematic release.
This version of airbyte-source-github might be problematic. Click here for more details.
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/PKG-INFO +2 -2
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/pyproject.toml +2 -2
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/config_migrations.py +4 -1
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/errors_handlers.py +1 -1
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/streams.py +37 -31
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/utils.py +8 -7
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/README.md +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/__init__.py +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/backoff_strategies.py +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/constants.py +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/github_schema.py +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/graphql.py +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/run.py +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/assignees.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/branches.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/collaborators.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/comments.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/commit_comment_reactions.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/commit_comments.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/commits.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/contributor_activity.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/deployments.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/events.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/issue_comment_reactions.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/issue_events.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/issue_labels.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/issue_milestones.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/issue_reactions.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/issue_timeline_events.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/issues.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/organizations.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/project_cards.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/project_columns.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/projects.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/projects_v2.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/pull_request_comment_reactions.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/pull_request_commits.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/pull_request_stats.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/pull_requests.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/releases.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/repositories.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/review_comments.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/reviews.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/shared/events/comment.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/shared/events/commented.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/shared/events/committed.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/shared/events/cross_referenced.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/shared/events/reviewed.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/shared/reaction.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/shared/reactions.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/shared/user.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/shared/user_graphql.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/stargazers.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/tags.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/team_members.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/team_memberships.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/teams.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/users.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/workflow_jobs.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/workflow_runs.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/workflows.json +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/source.py +0 -0
- {airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/spec.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: airbyte-source-github
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0rc1
|
|
4
4
|
Summary: Source implementation for GitHub.
|
|
5
5
|
Home-page: https://airbyte.com
|
|
6
6
|
License: MIT
|
|
@@ -11,7 +11,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Requires-Dist: airbyte-cdk (>=
|
|
14
|
+
Requires-Dist: airbyte-cdk (>=7,<8)
|
|
15
15
|
Requires-Dist: sgqlc (==16.3)
|
|
16
16
|
Project-URL: Documentation, https://docs.airbyte.com/integrations/sources/github
|
|
17
17
|
Project-URL: Repository, https://github.com/airbytehq/airbyte
|
|
@@ -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 = "1.
|
|
6
|
+
version = "1.9.0-rc.1"
|
|
7
7
|
name = "airbyte-source-github"
|
|
8
8
|
description = "Source implementation for GitHub."
|
|
9
9
|
authors = [ "Airbyte <contact@airbyte.io>",]
|
|
@@ -17,7 +17,7 @@ include = "source_github"
|
|
|
17
17
|
|
|
18
18
|
[tool.poetry.dependencies]
|
|
19
19
|
python = "^3.10,<3.12"
|
|
20
|
-
airbyte-cdk = "^
|
|
20
|
+
airbyte-cdk = "^7"
|
|
21
21
|
sgqlc = "==16.3"
|
|
22
22
|
|
|
23
23
|
[tool.poetry.scripts]
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/config_migrations.py
RENAMED
|
@@ -6,8 +6,11 @@ import logging
|
|
|
6
6
|
from abc import ABC
|
|
7
7
|
from typing import Any, List, Mapping
|
|
8
8
|
|
|
9
|
+
import orjson
|
|
10
|
+
|
|
9
11
|
from airbyte_cdk.config_observation import create_connector_config_control_message
|
|
10
12
|
from airbyte_cdk.entrypoint import AirbyteEntrypoint
|
|
13
|
+
from airbyte_cdk.models import AirbyteMessageSerializer
|
|
11
14
|
from airbyte_cdk.sources.message import InMemoryMessageRepository, MessageRepository
|
|
12
15
|
|
|
13
16
|
from .source import SourceGithub
|
|
@@ -72,7 +75,7 @@ class MigrateStringToArray(ABC):
|
|
|
72
75
|
cls.message_repository.emit_message(create_connector_config_control_message(migrated_config))
|
|
73
76
|
# emit the Airbyte Control Message from message queue to stdout
|
|
74
77
|
for message in cls.message_repository._message_queue:
|
|
75
|
-
print(message.
|
|
78
|
+
print(orjson.dumps(AirbyteMessageSerializer.dump(message)).decode())
|
|
76
79
|
|
|
77
80
|
@classmethod
|
|
78
81
|
def migrate(cls, args: List[str], source: SourceGithub) -> None:
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/errors_handlers.py
RENAMED
|
@@ -6,10 +6,10 @@ from typing import Optional, Union
|
|
|
6
6
|
|
|
7
7
|
import requests
|
|
8
8
|
|
|
9
|
+
from airbyte_cdk.models import FailureType
|
|
9
10
|
from airbyte_cdk.sources.streams.http import HttpStream
|
|
10
11
|
from airbyte_cdk.sources.streams.http.error_handlers import ErrorHandler, ErrorResolution, HttpStatusErrorHandler, ResponseAction
|
|
11
12
|
from airbyte_cdk.sources.streams.http.error_handlers.default_error_mapping import DEFAULT_ERROR_MAPPING
|
|
12
|
-
from airbyte_protocol.models import FailureType
|
|
13
13
|
|
|
14
14
|
from . import constants
|
|
15
15
|
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
|
+
from datetime import timedelta
|
|
7
8
|
from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Union
|
|
8
9
|
from urllib import parse
|
|
9
10
|
|
|
10
|
-
import pendulum
|
|
11
11
|
import requests
|
|
12
12
|
|
|
13
13
|
from airbyte_cdk import BackoffStrategy, StreamSlice
|
|
14
|
-
from airbyte_cdk.models import AirbyteLogMessage, AirbyteMessage, Level, SyncMode
|
|
14
|
+
from airbyte_cdk.models import AirbyteLogMessage, AirbyteMessage, FailureType, Level, SyncMode
|
|
15
15
|
from airbyte_cdk.models import Type as MessageType
|
|
16
16
|
from airbyte_cdk.sources.streams.availability_strategy import AvailabilityStrategy
|
|
17
17
|
from airbyte_cdk.sources.streams.checkpoint.substream_resumable_full_refresh_cursor import SubstreamResumableFullRefreshCursor
|
|
@@ -19,8 +19,9 @@ from airbyte_cdk.sources.streams.core import CheckpointMixin, Stream
|
|
|
19
19
|
from airbyte_cdk.sources.streams.http import HttpStream
|
|
20
20
|
from airbyte_cdk.sources.streams.http.error_handlers import ErrorHandler, ErrorResolution, HttpStatusErrorHandler, ResponseAction
|
|
21
21
|
from airbyte_cdk.sources.streams.http.exceptions import DefaultBackoffException, UserDefinedBackoffException
|
|
22
|
+
from airbyte_cdk.sources.streams.http.http_client import MessageRepresentationAirbyteTracedErrors
|
|
22
23
|
from airbyte_cdk.utils import AirbyteTracedException
|
|
23
|
-
from
|
|
24
|
+
from airbyte_cdk.utils.datetime_helpers import ab_datetime_format, ab_datetime_parse
|
|
24
25
|
|
|
25
26
|
from . import constants
|
|
26
27
|
from .backoff_strategies import ContributorActivityBackoffStrategy, GithubStreamABCBackoffStrategy
|
|
@@ -128,11 +129,14 @@ class GithubStreamABC(HttpStream, ABC):
|
|
|
128
129
|
# Reading records while handling the errors
|
|
129
130
|
try:
|
|
130
131
|
yield from super().read_records(stream_slice=stream_slice, **kwargs)
|
|
131
|
-
|
|
132
|
+
# HTTP Client wraps DefaultBackoffException into MessageRepresentationAirbyteTracedErrors
|
|
133
|
+
except MessageRepresentationAirbyteTracedErrors as e:
|
|
132
134
|
# This whole try/except situation in `read_records()` isn't good but right now in `self._send_request()`
|
|
133
135
|
# function we have `response.raise_for_status()` so we don't have much choice on how to handle errors.
|
|
134
136
|
# Bocked on https://github.com/airbytehq/airbyte/issues/3514.
|
|
135
|
-
if e
|
|
137
|
+
if not hasattr(e, "_exception") and not hasattr(e._exception, "response"):
|
|
138
|
+
raise e
|
|
139
|
+
if e._exception.response.status_code == requests.codes.NOT_FOUND:
|
|
136
140
|
# A lot of streams are not available for repositories owned by a user instead of an organization.
|
|
137
141
|
if isinstance(self, Organizations):
|
|
138
142
|
error_msg = f"Syncing `{self.__class__.__name__}` stream isn't available for organization `{organisation}`."
|
|
@@ -140,8 +144,8 @@ class GithubStreamABC(HttpStream, ABC):
|
|
|
140
144
|
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..."
|
|
141
145
|
else:
|
|
142
146
|
error_msg = f"Syncing `{self.__class__.__name__}` stream isn't available for repository `{repository}`."
|
|
143
|
-
elif e.response.status_code == requests.codes.FORBIDDEN:
|
|
144
|
-
error_msg = str(e.response.json().get("message"))
|
|
147
|
+
elif e._exception.response.status_code == requests.codes.FORBIDDEN:
|
|
148
|
+
error_msg = str(e._exception.response.json().get("message"))
|
|
145
149
|
# When using the `check_connection` method, we should raise an error if we do not have access to the repository.
|
|
146
150
|
if isinstance(self, Repositories):
|
|
147
151
|
raise e
|
|
@@ -157,27 +161,27 @@ class GithubStreamABC(HttpStream, ABC):
|
|
|
157
161
|
error_msg = (
|
|
158
162
|
f"Syncing `{self.name}` stream isn't available for repository `{repository}`. Full error message: {error_msg}"
|
|
159
163
|
)
|
|
160
|
-
elif e.response.status_code == requests.codes.UNAUTHORIZED:
|
|
164
|
+
elif e._exception.response.status_code == requests.codes.UNAUTHORIZED:
|
|
161
165
|
if self.access_token_type == constants.PERSONAL_ACCESS_TOKEN_TITLE:
|
|
162
|
-
error_msg = str(e.response.json().get("message"))
|
|
166
|
+
error_msg = str(e._exception.response.json().get("message"))
|
|
163
167
|
self.logger.error(f"{self.access_token_type} renewal is required: {error_msg}")
|
|
164
168
|
raise e
|
|
165
|
-
elif e.response.status_code == requests.codes.GONE and isinstance(self, Projects):
|
|
169
|
+
elif e._exception.response.status_code == requests.codes.GONE and isinstance(self, Projects):
|
|
166
170
|
# Some repos don't have projects enabled and we we get "410 Client Error: Gone for
|
|
167
171
|
# url: https://api.github.com/repos/xyz/projects?per_page=100" error.
|
|
168
172
|
error_msg = f"Syncing `Projects` stream isn't available for repository `{stream_slice['repository']}`."
|
|
169
|
-
elif e.response.status_code == requests.codes.CONFLICT:
|
|
173
|
+
elif e._exception.response.status_code == requests.codes.CONFLICT:
|
|
170
174
|
error_msg = (
|
|
171
175
|
f"Syncing `{self.name}` stream isn't available for repository "
|
|
172
176
|
f"`{stream_slice['repository']}`, it seems like this repository is empty."
|
|
173
177
|
)
|
|
174
|
-
elif e.response.status_code == requests.codes.SERVER_ERROR and isinstance(self, WorkflowRuns):
|
|
178
|
+
elif e._exception.response.status_code == requests.codes.SERVER_ERROR and isinstance(self, WorkflowRuns):
|
|
175
179
|
error_msg = f"Syncing `{self.name}` stream isn't available for repository `{stream_slice['repository']}`."
|
|
176
|
-
elif e.response.status_code == requests.codes.BAD_GATEWAY:
|
|
180
|
+
elif e._exception.response.status_code == requests.codes.BAD_GATEWAY:
|
|
177
181
|
error_msg = f"Stream {self.name} temporary failed. Try to re-run sync later"
|
|
178
182
|
else:
|
|
179
183
|
# most probably here we're facing a 500 server error and a risk to get a non-json response, so lets output response.text
|
|
180
|
-
self.logger.error(f"Undefined error while reading records: {e.response.text}")
|
|
184
|
+
self.logger.error(f"Undefined error while reading records: {e._exception.response.text}")
|
|
181
185
|
raise e
|
|
182
186
|
|
|
183
187
|
self.logger.warning(error_msg)
|
|
@@ -1437,7 +1441,7 @@ class Workflows(SemiIncrementalMixin, GithubStream):
|
|
|
1437
1441
|
yield self.transform(record=record, stream_slice=stream_slice)
|
|
1438
1442
|
|
|
1439
1443
|
def convert_cursor_value(self, value):
|
|
1440
|
-
return
|
|
1444
|
+
return ab_datetime_format(value, "YYYY-MM-DDTHH:mm:ss[Z]")
|
|
1441
1445
|
|
|
1442
1446
|
|
|
1443
1447
|
class WorkflowRuns(SemiIncrementalMixin, GithubStream):
|
|
@@ -1478,7 +1482,7 @@ class WorkflowRuns(SemiIncrementalMixin, GithubStream):
|
|
|
1478
1482
|
# the state is updated only in the end of the sync as records are sorted in reverse order
|
|
1479
1483
|
new_state = self.state
|
|
1480
1484
|
if start_point:
|
|
1481
|
-
break_point = (
|
|
1485
|
+
break_point = (ab_datetime_parse(start_point) - timedelta(days=self.re_run_period)).isoformat()
|
|
1482
1486
|
for record in super(SemiIncrementalMixin, self).read_records(
|
|
1483
1487
|
sync_mode=sync_mode, cursor_field=cursor_field, stream_slice=stream_slice, stream_state=stream_state
|
|
1484
1488
|
):
|
|
@@ -1663,22 +1667,24 @@ class ContributorActivity(GithubStream):
|
|
|
1663
1667
|
repository = stream_slice.get("repository", "")
|
|
1664
1668
|
try:
|
|
1665
1669
|
yield from super().read_records(stream_slice=stream_slice, **kwargs)
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1670
|
+
# HTTP Client wraps BackoffException into MessageRepresentationAirbyteTracedErrors
|
|
1671
|
+
except MessageRepresentationAirbyteTracedErrors as e:
|
|
1672
|
+
if hasattr(e, "_exception") and hasattr(e._exception, "response"):
|
|
1673
|
+
if e._exception.response.status_code == requests.codes.ACCEPTED:
|
|
1674
|
+
yield AirbyteMessage(
|
|
1675
|
+
type=MessageType.LOG,
|
|
1676
|
+
log=AirbyteLogMessage(
|
|
1677
|
+
level=Level.INFO,
|
|
1678
|
+
message=f"Syncing `{self.__class__.__name__}` " f"stream isn't available for repository `{repository}`.",
|
|
1679
|
+
),
|
|
1680
|
+
)
|
|
1675
1681
|
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
+
# In order to retain the existing stream behavior before we added RFR to this stream, we need to close out the
|
|
1683
|
+
# partition after we give up the maximum number of retries on the 202 response. This does lead to the question
|
|
1684
|
+
# of if we should prematurely exit in the first place, but for now we're going to aim for feature parity
|
|
1685
|
+
partition_obj = stream_slice.get("partition")
|
|
1686
|
+
if self.cursor and partition_obj:
|
|
1687
|
+
self.cursor.close_slice(StreamSlice(cursor_slice={}, partition=partition_obj))
|
|
1682
1688
|
else:
|
|
1683
1689
|
raise e
|
|
1684
1690
|
|
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
import time
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
+
from datetime import timedelta
|
|
7
8
|
from itertools import cycle
|
|
8
9
|
from typing import Any, List, Mapping
|
|
9
10
|
|
|
10
|
-
import pendulum
|
|
11
11
|
import requests
|
|
12
12
|
|
|
13
13
|
from airbyte_cdk.models import SyncMode
|
|
14
14
|
from airbyte_cdk.sources.streams import Stream
|
|
15
15
|
from airbyte_cdk.sources.streams.http.requests_native_auth import TokenAuthenticator
|
|
16
16
|
from airbyte_cdk.sources.streams.http.requests_native_auth.abstract_token import AbstractHeaderAuthenticator
|
|
17
|
+
from airbyte_cdk.utils.datetime_helpers import AirbyteDateTime, ab_datetime_now, ab_datetime_parse
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def getter(D: dict, key_or_keys, strict=True):
|
|
@@ -43,8 +44,8 @@ class GitHubAPILimitException(Exception):
|
|
|
43
44
|
class Token:
|
|
44
45
|
count_rest: int = 5000
|
|
45
46
|
count_graphql: int = 5000
|
|
46
|
-
reset_at_rest:
|
|
47
|
-
reset_at_graphql:
|
|
47
|
+
reset_at_rest: AirbyteDateTime = ab_datetime_now()
|
|
48
|
+
reset_at_graphql: AirbyteDateTime = ab_datetime_now()
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
class MultipleTokenAuthenticatorWithRateLimiter(AbstractHeaderAuthenticator):
|
|
@@ -55,7 +56,7 @@ class MultipleTokenAuthenticatorWithRateLimiter(AbstractHeaderAuthenticator):
|
|
|
55
56
|
the first token becomes available again.
|
|
56
57
|
"""
|
|
57
58
|
|
|
58
|
-
DURATION =
|
|
59
|
+
DURATION = timedelta(seconds=3600) # Duration at which the current rate limit window resets
|
|
59
60
|
|
|
60
61
|
def __init__(self, tokens: List[str], auth_method: str = "token", auth_header: str = "Authorization"):
|
|
61
62
|
self._auth_method = auth_method
|
|
@@ -125,13 +126,13 @@ class MultipleTokenAuthenticatorWithRateLimiter(AbstractHeaderAuthenticator):
|
|
|
125
126
|
remaining_info_core = rate_limit_info.get("core")
|
|
126
127
|
token_info.count_rest, token_info.reset_at_rest = (
|
|
127
128
|
remaining_info_core.get("remaining"),
|
|
128
|
-
|
|
129
|
+
ab_datetime_parse(remaining_info_core.get("reset")),
|
|
129
130
|
)
|
|
130
131
|
|
|
131
132
|
remaining_info_graphql = rate_limit_info.get("graphql")
|
|
132
133
|
token_info.count_graphql, token_info.reset_at_graphql = (
|
|
133
134
|
remaining_info_graphql.get("remaining"),
|
|
134
|
-
|
|
135
|
+
ab_datetime_parse(remaining_info_graphql.get("reset")),
|
|
135
136
|
)
|
|
136
137
|
|
|
137
138
|
def check_all_tokens(self):
|
|
@@ -143,7 +144,7 @@ class MultipleTokenAuthenticatorWithRateLimiter(AbstractHeaderAuthenticator):
|
|
|
143
144
|
setattr(current_token, count_attr, getattr(current_token, count_attr) - 1)
|
|
144
145
|
return True
|
|
145
146
|
elif all(getattr(x, count_attr) == 0 for x in self._tokens.values()):
|
|
146
|
-
min_time_to_wait = min((getattr(x, reset_attr) -
|
|
147
|
+
min_time_to_wait = min((getattr(x, reset_attr) - ab_datetime_now()).seconds for x in self._tokens.values())
|
|
147
148
|
if min_time_to_wait < self.max_time:
|
|
148
149
|
time.sleep(min_time_to_wait if min_time_to_wait > 0 else 0)
|
|
149
150
|
self.check_all_tokens()
|
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/backoff_strategies.py
RENAMED
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/github_schema.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/assignees.json
RENAMED
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/branches.json
RENAMED
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/comments.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/commits.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/events.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/issues.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/projects.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/releases.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/reviews.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/tags.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/teams.json
RENAMED
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/users.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{airbyte_source_github-1.8.41 → airbyte_source_github-1.9.0rc1}/source_github/schemas/workflows.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|