airbyte-source-github 1.5.7__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.
Files changed (88) hide show
  1. airbyte_source_github-1.5.7.dist-info/METADATA +144 -0
  2. airbyte_source_github-1.5.7.dist-info/RECORD +88 -0
  3. airbyte_source_github-1.5.7.dist-info/WHEEL +5 -0
  4. airbyte_source_github-1.5.7.dist-info/entry_points.txt +2 -0
  5. airbyte_source_github-1.5.7.dist-info/top_level.txt +3 -0
  6. integration_tests/__init__.py +0 -0
  7. integration_tests/abnormal_state.json +237 -0
  8. integration_tests/acceptance.py +16 -0
  9. integration_tests/configured_catalog.json +435 -0
  10. integration_tests/configured_catalog_full_refresh_test.json +415 -0
  11. integration_tests/invalid_config.json +5 -0
  12. integration_tests/sample_config.json +5 -0
  13. integration_tests/sample_state.json +137 -0
  14. source_github/__init__.py +27 -0
  15. source_github/config_migrations.py +106 -0
  16. source_github/constants.py +9 -0
  17. source_github/github_schema.py +41034 -0
  18. source_github/graphql.py +327 -0
  19. source_github/run.py +17 -0
  20. source_github/schemas/assignees.json +63 -0
  21. source_github/schemas/branches.json +48 -0
  22. source_github/schemas/collaborators.json +80 -0
  23. source_github/schemas/comments.json +104 -0
  24. source_github/schemas/commit_comment_reactions.json +4 -0
  25. source_github/schemas/commit_comments.json +53 -0
  26. source_github/schemas/commits.json +126 -0
  27. source_github/schemas/contributor_activity.json +109 -0
  28. source_github/schemas/deployments.json +77 -0
  29. source_github/schemas/events.json +63 -0
  30. source_github/schemas/issue_comment_reactions.json +4 -0
  31. source_github/schemas/issue_events.json +335 -0
  32. source_github/schemas/issue_labels.json +30 -0
  33. source_github/schemas/issue_milestones.json +61 -0
  34. source_github/schemas/issue_reactions.json +28 -0
  35. source_github/schemas/issue_timeline_events.json +1056 -0
  36. source_github/schemas/issues.json +281 -0
  37. source_github/schemas/organizations.json +197 -0
  38. source_github/schemas/project_cards.json +50 -0
  39. source_github/schemas/project_columns.json +38 -0
  40. source_github/schemas/projects.json +50 -0
  41. source_github/schemas/projects_v2.json +80 -0
  42. source_github/schemas/pull_request_comment_reactions.json +28 -0
  43. source_github/schemas/pull_request_commits.json +122 -0
  44. source_github/schemas/pull_request_stats.json +84 -0
  45. source_github/schemas/pull_requests.json +363 -0
  46. source_github/schemas/releases.json +126 -0
  47. source_github/schemas/repositories.json +313 -0
  48. source_github/schemas/review_comments.json +118 -0
  49. source_github/schemas/reviews.json +69 -0
  50. source_github/schemas/shared/events/comment.json +188 -0
  51. source_github/schemas/shared/events/commented.json +118 -0
  52. source_github/schemas/shared/events/committed.json +56 -0
  53. source_github/schemas/shared/events/cross_referenced.json +784 -0
  54. source_github/schemas/shared/events/reviewed.json +139 -0
  55. source_github/schemas/shared/reaction.json +27 -0
  56. source_github/schemas/shared/reactions.json +35 -0
  57. source_github/schemas/shared/user.json +59 -0
  58. source_github/schemas/shared/user_graphql.json +26 -0
  59. source_github/schemas/stargazers.json +19 -0
  60. source_github/schemas/tags.json +32 -0
  61. source_github/schemas/team_members.json +66 -0
  62. source_github/schemas/team_memberships.json +24 -0
  63. source_github/schemas/teams.json +50 -0
  64. source_github/schemas/users.json +63 -0
  65. source_github/schemas/workflow_jobs.json +109 -0
  66. source_github/schemas/workflow_runs.json +449 -0
  67. source_github/schemas/workflows.json +41 -0
  68. source_github/source.py +339 -0
  69. source_github/spec.json +179 -0
  70. source_github/streams.py +1678 -0
  71. source_github/utils.py +152 -0
  72. unit_tests/__init__.py +3 -0
  73. unit_tests/conftest.py +29 -0
  74. unit_tests/projects_v2_pull_requests_query.json +3 -0
  75. unit_tests/pull_request_stats_query.json +3 -0
  76. unit_tests/responses/contributor_activity_response.json +33 -0
  77. unit_tests/responses/graphql_reviews_responses.json +405 -0
  78. unit_tests/responses/issue_timeline_events.json +166 -0
  79. unit_tests/responses/issue_timeline_events_response.json +170 -0
  80. unit_tests/responses/projects_v2_response.json +45 -0
  81. unit_tests/responses/pull_request_comment_reactions.json +744 -0
  82. unit_tests/responses/pull_request_stats_response.json +317 -0
  83. unit_tests/test_migrations/test_config.json +8 -0
  84. unit_tests/test_migrations/test_new_config.json +8 -0
  85. unit_tests/test_multiple_token_authenticator.py +160 -0
  86. unit_tests/test_source.py +326 -0
  87. unit_tests/test_stream.py +1471 -0
  88. unit_tests/utils.py +78 -0
@@ -0,0 +1,317 @@
1
+ {
2
+ "data": {
3
+ "repository": {
4
+ "name": "integration-test",
5
+ "owner": {
6
+ "login": "airbytehq"
7
+ },
8
+ "pullRequests": {
9
+ "nodes": [
10
+ {
11
+ "node_id": "MDExOlB1bGxSZXF1ZXN0NzIxNDM1NTA2",
12
+ "id": 721435506,
13
+ "number": 5,
14
+ "updated_at": "2021-08-27T15:53:14Z",
15
+ "changed_files": 5,
16
+ "deletions": 0,
17
+ "additions": 5,
18
+ "merged": false,
19
+ "mergeable": "MERGEABLE",
20
+ "can_be_rebased": true,
21
+ "maintainer_can_modify": false,
22
+ "merge_state_status": "BLOCKED",
23
+ "comments": {
24
+ "totalCount": 0
25
+ },
26
+ "commits": {
27
+ "totalCount": 5
28
+ },
29
+ "review_comments": {
30
+ "totalCount": 1,
31
+ "nodes": [
32
+ {
33
+ "comments": {
34
+ "totalCount": 0
35
+ }
36
+ }
37
+ ]
38
+ },
39
+ "merged_by": null
40
+ },
41
+ {
42
+ "node_id": "MDExOlB1bGxSZXF1ZXN0NzIxNDM1NDA3",
43
+ "id": 721435407,
44
+ "number": 2,
45
+ "updated_at": "2021-08-27T15:53:27Z",
46
+ "changed_files": 5,
47
+ "deletions": 0,
48
+ "additions": 5,
49
+ "merged": false,
50
+ "mergeable": "MERGEABLE",
51
+ "can_be_rebased": true,
52
+ "maintainer_can_modify": false,
53
+ "merge_state_status": "BLOCKED",
54
+ "comments": {
55
+ "totalCount": 0
56
+ },
57
+ "commits": {
58
+ "totalCount": 5
59
+ },
60
+ "review_comments": {
61
+ "totalCount": 1,
62
+ "nodes": [
63
+ {
64
+ "comments": {
65
+ "totalCount": 0
66
+ }
67
+ }
68
+ ]
69
+ },
70
+ "merged_by": null
71
+ },
72
+ {
73
+ "node_id": "MDExOlB1bGxSZXF1ZXN0NzIxNDM1NDQw",
74
+ "id": 721435440,
75
+ "number": 3,
76
+ "updated_at": "2021-08-27T16:02:49Z",
77
+ "changed_files": 5,
78
+ "deletions": 0,
79
+ "additions": 5,
80
+ "merged": true,
81
+ "mergeable": "UNKNOWN",
82
+ "can_be_rebased": false,
83
+ "maintainer_can_modify": false,
84
+ "merge_state_status": "UNKNOWN",
85
+ "comments": {
86
+ "totalCount": 0
87
+ },
88
+ "commits": {
89
+ "totalCount": 5
90
+ },
91
+ "review_comments": {
92
+ "totalCount": 1,
93
+ "nodes": [
94
+ {
95
+ "comments": {
96
+ "totalCount": 0
97
+ }
98
+ }
99
+ ]
100
+ },
101
+ "merged_by": {
102
+ "__typename": "User",
103
+ "node_id": "MDQ6VXNlcjc0MzkwMQ==",
104
+ "id": 743901,
105
+ "login": "gaart",
106
+ "avatar_url": "https://avatars.githubusercontent.com/u/743901?v=4",
107
+ "html_url": "https://github.com/gaart",
108
+ "site_admin": false
109
+ }
110
+ },
111
+ {
112
+ "node_id": "MDExOlB1bGxSZXF1ZXN0NzIxNDM1NDY2",
113
+ "id": 721435466,
114
+ "number": 4,
115
+ "updated_at": "2021-08-31T12:01:15Z",
116
+ "changed_files": 5,
117
+ "deletions": 0,
118
+ "additions": 5,
119
+ "merged": false,
120
+ "mergeable": "MERGEABLE",
121
+ "can_be_rebased": true,
122
+ "maintainer_can_modify": false,
123
+ "merge_state_status": "BLOCKED",
124
+ "comments": {
125
+ "totalCount": 0
126
+ },
127
+ "commits": {
128
+ "totalCount": 5
129
+ },
130
+ "review_comments": {
131
+ "totalCount": 3,
132
+ "nodes": [
133
+ {
134
+ "comments": {
135
+ "totalCount": 0
136
+ }
137
+ },
138
+ {
139
+ "comments": {
140
+ "totalCount": 0
141
+ }
142
+ },
143
+ {
144
+ "comments": {
145
+ "totalCount": 1
146
+ }
147
+ }
148
+ ]
149
+ },
150
+ "merged_by": null
151
+ },
152
+ {
153
+ "node_id": "PR_kwDOF9hP9c4xmEi6",
154
+ "id": 832063674,
155
+ "number": 12,
156
+ "updated_at": "2022-01-26T03:46:56Z",
157
+ "changed_files": 1,
158
+ "deletions": 0,
159
+ "additions": 2,
160
+ "merged": true,
161
+ "mergeable": "UNKNOWN",
162
+ "can_be_rebased": false,
163
+ "maintainer_can_modify": false,
164
+ "merge_state_status": "UNKNOWN",
165
+ "comments": {
166
+ "totalCount": 0
167
+ },
168
+ "commits": {
169
+ "totalCount": 1
170
+ },
171
+ "review_comments": {
172
+ "totalCount": 1,
173
+ "nodes": [
174
+ {
175
+ "comments": {
176
+ "totalCount": 0
177
+ }
178
+ }
179
+ ]
180
+ },
181
+ "merged_by": {
182
+ "__typename": "User",
183
+ "node_id": "MDQ6VXNlcjUxNTQzMjI=",
184
+ "id": 5154322,
185
+ "login": "marcosmarxm",
186
+ "avatar_url": "https://avatars.githubusercontent.com/u/5154322?u=92c89b82271d48f41fad03923b0a24083e049038&v=4",
187
+ "html_url": "https://github.com/marcosmarxm",
188
+ "site_admin": false
189
+ }
190
+ },
191
+ {
192
+ "node_id": "MDExOlB1bGxSZXF1ZXN0NzIxNDM1Mzcz",
193
+ "id": 721435373,
194
+ "number": 1,
195
+ "updated_at": "2022-03-31T11:06:06Z",
196
+ "changed_files": 5,
197
+ "deletions": 0,
198
+ "additions": 5,
199
+ "merged": false,
200
+ "mergeable": "MERGEABLE",
201
+ "can_be_rebased": true,
202
+ "maintainer_can_modify": false,
203
+ "merge_state_status": "BLOCKED",
204
+ "comments": {
205
+ "totalCount": 0
206
+ },
207
+ "commits": {
208
+ "totalCount": 5
209
+ },
210
+ "review_comments": {
211
+ "totalCount": 1,
212
+ "nodes": [
213
+ {
214
+ "comments": {
215
+ "totalCount": 0
216
+ }
217
+ }
218
+ ]
219
+ },
220
+ "merged_by": null
221
+ },
222
+ {
223
+ "node_id": "PR_kwDOF9hP9c46s2Qa",
224
+ "id": 984835098,
225
+ "number": 14,
226
+ "updated_at": "2022-10-04T17:41:29Z",
227
+ "changed_files": 1,
228
+ "deletions": 0,
229
+ "additions": 1,
230
+ "merged": false,
231
+ "mergeable": "MERGEABLE",
232
+ "can_be_rebased": true,
233
+ "maintainer_can_modify": false,
234
+ "merge_state_status": "BLOCKED",
235
+ "comments": {
236
+ "totalCount": 0
237
+ },
238
+ "commits": {
239
+ "totalCount": 2
240
+ },
241
+ "review_comments": {
242
+ "totalCount": 1,
243
+ "nodes": [
244
+ {
245
+ "comments": {
246
+ "totalCount": 0
247
+ }
248
+ }
249
+ ]
250
+ },
251
+ "merged_by": null
252
+ },
253
+ {
254
+ "node_id": "PR_kwDOF9hP9c41Vftv",
255
+ "id": 894827375,
256
+ "number": 13,
257
+ "updated_at": "2023-05-03T06:50:23Z",
258
+ "changed_files": 1,
259
+ "deletions": 1,
260
+ "additions": 1,
261
+ "merged": false,
262
+ "mergeable": "MERGEABLE",
263
+ "can_be_rebased": true,
264
+ "maintainer_can_modify": false,
265
+ "merge_state_status": "BLOCKED",
266
+ "comments": {
267
+ "totalCount": 0
268
+ },
269
+ "commits": {
270
+ "totalCount": 1
271
+ },
272
+ "review_comments": {
273
+ "totalCount": 0,
274
+ "nodes": []
275
+ },
276
+ "merged_by": null
277
+ },
278
+ {
279
+ "node_id": "PR_kwDOF9hP9c5PqZhG",
280
+ "id": 1336514630,
281
+ "number": 15,
282
+ "updated_at": "2023-05-04T10:07:46Z",
283
+ "changed_files": 1,
284
+ "deletions": 0,
285
+ "additions": 1,
286
+ "merged": false,
287
+ "mergeable": "MERGEABLE",
288
+ "can_be_rebased": false,
289
+ "maintainer_can_modify": false,
290
+ "merge_state_status": "BLOCKED",
291
+ "comments": {
292
+ "totalCount": 1
293
+ },
294
+ "commits": {
295
+ "totalCount": 1
296
+ },
297
+ "review_comments": {
298
+ "totalCount": 1,
299
+ "nodes": [
300
+ {
301
+ "comments": {
302
+ "totalCount": 0
303
+ }
304
+ }
305
+ ]
306
+ },
307
+ "merged_by": null
308
+ }
309
+ ],
310
+ "pageInfo": {
311
+ "hasNextPage": false,
312
+ "endCursor": "Y3Vyc29yOnYyOpK5MjAyMy0wNS0wNFQxMzowNzo0NiswMzowMM5PqZhG"
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "credentials": {
3
+ "personal_access_token": "personal_access_token"
4
+ },
5
+ "repository": "airbytehq/airbyte airbytehq/airbyte-platform",
6
+ "start_date": "2000-01-01T00:00:00Z",
7
+ "branch": "airbytehq/airbyte/master airbytehq/airbyte-platform/main"
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "credentials": {
3
+ "personal_access_token": "personal_access_token"
4
+ },
5
+ "repositories": ["airbytehq/airbyte", "airbytehq/airbyte-platform"],
6
+ "start_date": "2000-01-01T00:00:00Z",
7
+ "branches": ["airbytehq/airbyte/master", "airbytehq/airbyte-platform/main"]
8
+ }
@@ -0,0 +1,160 @@
1
+ #
2
+ # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
+ #
4
+
5
+ import json
6
+ from unittest.mock import patch
7
+
8
+ import pendulum
9
+ import responses
10
+ from freezegun import freeze_time
11
+ from source_github import SourceGithub
12
+ from source_github.streams import Organizations
13
+ from source_github.utils import MultipleTokenAuthenticatorWithRateLimiter, read_full_refresh
14
+
15
+
16
+ @responses.activate
17
+ def test_multiple_tokens(rate_limit_mock_response):
18
+ authenticator = SourceGithub()._get_authenticator({"access_token": "token_1, token_2, token_3"})
19
+ assert isinstance(authenticator, MultipleTokenAuthenticatorWithRateLimiter)
20
+ assert ["token_1", "token_2", "token_3"] == list(authenticator._tokens)
21
+
22
+
23
+ @responses.activate
24
+ def test_authenticator_counter(rate_limit_mock_response):
25
+ """
26
+ This test ensures that the rate limiter:
27
+ 1. correctly handles the available limits from GitHub API and saves it.
28
+ 2. correctly counts the number of requests made.
29
+ """
30
+ authenticator = MultipleTokenAuthenticatorWithRateLimiter(tokens=["token1", "token2", "token3"])
31
+
32
+ assert [(x.count_rest, x.count_graphql) for x in authenticator._tokens.values()] == [(5000, 5000), (5000, 5000), (5000, 5000)]
33
+ organization_args = {"organizations": ["org1", "org2"], "authenticator": authenticator}
34
+ stream = Organizations(**organization_args)
35
+ responses.add("GET", "https://api.github.com/orgs/org1", json={"id": 1})
36
+ responses.add("GET", "https://api.github.com/orgs/org2", json={"id": 2})
37
+ list(read_full_refresh(stream))
38
+ assert authenticator._tokens["token1"].count_rest == 4998
39
+
40
+
41
+ @responses.activate
42
+ def test_multiple_token_authenticator_with_rate_limiter(caplog):
43
+ """
44
+ This test ensures that:
45
+ 1. The rate limiter iterates over all tokens one-by-one after the previous is fully drained.
46
+ 2. Counter is set to zero after 1500 requests were made. (500 available requests per key were set as default)
47
+ 3. Exception is handled and log warning message could be found in output. Connector does not raise AirbyteTracedException because there might be GraphQL streams with remaining request we still can read.
48
+ """
49
+
50
+ counter_rate_limits = 0
51
+ counter_orgs = 0
52
+
53
+ def request_callback_rate_limits(request):
54
+ nonlocal counter_rate_limits
55
+ while counter_rate_limits < 3:
56
+ counter_rate_limits += 1
57
+ resp_body = {
58
+ "resources": {
59
+ "core": {
60
+ "limit": 500,
61
+ "used": 0,
62
+ "remaining": 500,
63
+ "reset": 4070908800
64
+ },
65
+ "graphql": {
66
+ "limit": 500,
67
+ "used": 0,
68
+ "remaining": 500,
69
+ "reset": 4070908800
70
+ }
71
+ }
72
+ }
73
+ return (200, {}, json.dumps(resp_body))
74
+
75
+ responses.add_callback(responses.GET, "https://api.github.com/rate_limit", callback=request_callback_rate_limits)
76
+ authenticator = MultipleTokenAuthenticatorWithRateLimiter(tokens=["token1", "token2", "token3"])
77
+ organization_args = {"organizations": ["org1"], "authenticator": authenticator}
78
+ stream = Organizations(**organization_args)
79
+
80
+ def request_callback_orgs(request):
81
+ nonlocal counter_orgs
82
+ while counter_orgs < 1_501:
83
+ counter_orgs += 1
84
+ resp_body = {"id": 1}
85
+ headers = {"Link": '<https://api.github.com/orgs/org1?page=2>; rel="next"'}
86
+ return (200, headers, json.dumps(resp_body))
87
+
88
+ responses.add_callback(
89
+ responses.GET,
90
+ "https://api.github.com/orgs/org1",
91
+ callback=request_callback_orgs,
92
+ content_type="application/json",
93
+ )
94
+
95
+ list(read_full_refresh(stream))
96
+ assert [(x.count_rest, x.count_graphql) for x in authenticator._tokens.values()] == [(0, 500), (0, 500), (0, 500)]
97
+ assert "Stream: `organizations`, slice: `{'organization': 'org1'}`. Limits for all provided tokens are reached, please try again later" in caplog.messages
98
+
99
+
100
+ @freeze_time("2021-01-01 12:00:00")
101
+ @responses.activate
102
+ @patch("time.sleep")
103
+ def test_multiple_token_authenticator_with_rate_limiter_and_sleep(sleep_mock, caplog):
104
+ """
105
+ This test ensures that:
106
+ 1. The rate limiter will only wait (sleep) for token availability if the nearest available token appears within 600 seconds (see max_time).
107
+ 2. Token Counter is reset to new values after 1500 requests were made and last token is still in use.
108
+ """
109
+
110
+ counter_rate_limits = 0
111
+ counter_orgs = 0
112
+ ACCEPTED_WAITING_TIME_IN_SECONDS = 595
113
+ reset_time = (pendulum.now() + pendulum.duration(seconds=ACCEPTED_WAITING_TIME_IN_SECONDS)).int_timestamp
114
+
115
+ def request_callback_rate_limits(request):
116
+ nonlocal counter_rate_limits
117
+ while counter_rate_limits < 6:
118
+ counter_rate_limits += 1
119
+ resp_body = {
120
+ "resources": {
121
+ "core": {
122
+ "limit": 500,
123
+ "used": 0,
124
+ "remaining": 500,
125
+ "reset": reset_time
126
+ },
127
+ "graphql": {
128
+ "limit": 500,
129
+ "used": 0,
130
+ "remaining": 500,
131
+ "reset": reset_time
132
+ }
133
+ }
134
+ }
135
+ return (200, {}, json.dumps(resp_body))
136
+
137
+ responses.add_callback(responses.GET, "https://api.github.com/rate_limit", callback=request_callback_rate_limits)
138
+ authenticator = MultipleTokenAuthenticatorWithRateLimiter(tokens=["token1", "token2", "token3"])
139
+ organization_args = {"organizations": ["org1"], "authenticator": authenticator}
140
+ stream = Organizations(**organization_args)
141
+
142
+ def request_callback_orgs(request):
143
+ nonlocal counter_orgs
144
+ while counter_orgs < 1_501:
145
+ counter_orgs += 1
146
+ resp_body = {"id": 1}
147
+ headers = {"Link": '<https://api.github.com/orgs/org1?page=2>; rel="next"'}
148
+ return (200, headers, json.dumps(resp_body))
149
+ return (200, {}, json.dumps({"id": 2}))
150
+
151
+ responses.add_callback(
152
+ responses.GET,
153
+ "https://api.github.com/orgs/org1",
154
+ callback=request_callback_orgs,
155
+ content_type="application/json",
156
+ )
157
+
158
+ list(read_full_refresh(stream))
159
+ sleep_mock.assert_called_once_with(ACCEPTED_WAITING_TIME_IN_SECONDS)
160
+ assert [(x.count_rest, x.count_graphql) for x in authenticator._tokens.values()] == [(500, 500), (500, 500), (498, 500)]