osgithub 0.5.0__tar.gz → 0.6.0__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.
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: osgithub
3
+ Version: 0.6.0
4
+ Home-page: https://github.com/opensafely-core/osgithub
5
+ Author: OpenSAFELY
6
+ Author-email: tech@opensafely.org
7
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
8
+ Requires-Python: >=3.9
9
+ License-File: LICENSE
10
+ Requires-Dist: requests
11
+ Requires-Dist: requests-cache
12
+ Requires-Dist: furl
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: classifier
16
+ Dynamic: home-page
17
+ Dynamic: license-file
18
+ Dynamic: requires-dist
19
+ Dynamic: requires-python
@@ -3,109 +3,131 @@
3
3
  A thin wrapper around the GitHub API.
4
4
 
5
5
  ## Environment
6
+
6
7
  Set the following environment variables:
7
- - `GITHUB_USER_AGENT` - a string to identify your application
8
- - `GITHUB_TOKEN` - optional; default token to use.
9
- - `REQUESTS_CACHE_NAME` - optional, defaults to "http_cache"
8
+
9
+ - `GITHUB_USER_AGENT` - a string to identify your application
10
+ - `GITHUB_TOKEN` - optional; default token to use.
11
+ - `REQUESTS_CACHE_NAME` - optional, defaults to "http_cache"
10
12
 
11
13
  ## Usage
12
14
 
13
15
  ### Create a client
14
- ```
16
+
17
+ ```py
15
18
  from github_api_cache import GithubClient
16
19
  # Use the default token, if one is set in the environment.
17
20
  client = GithubClient()
18
21
  ```
19
22
 
20
23
  ### get a repo (returns a GithubRepo)
21
- ```
24
+
25
+ ```py
22
26
  repo = client.get_repo("opensafely-core/osgithub")
23
27
  ```
24
28
 
25
29
  #### get a list of branches
26
- ```
30
+
31
+ ```py
27
32
  repo.get_branches()
28
33
  ```
34
+
29
35
  And branch count:
30
- ```
36
+
37
+ ```py
31
38
  repo.branch_count
32
39
  ```
33
40
 
34
41
  #### get a list of pull requests
42
+
35
43
  Provide a `page` argument to get more pages than just the first one (30 result are returned per page).
36
- ```
44
+
45
+ ```py
37
46
  repo.get_pull_requests(page=1)
38
47
  ```
39
48
 
40
- By default, this fetches open PRs only. To retrieve other states, pass `state="closed"` or `state="open"`
49
+ By default, this fetches open PRs only. To retrieve other states, pass `state="closed"` or `state="open"`
41
50
 
42
51
  Pull request counts:
43
- ```
52
+
53
+ ```py
44
54
  repo.pull_request_count
45
55
  repo.open_pull_request_count
46
56
  ```
47
57
 
48
58
  #### get the contents of the `osgithub` directory on branch `main`
49
- ```
59
+
60
+ ```py
50
61
  # returns a list of GithubContentFile objects
51
62
  repo.get_contents("osgithub", "main")
52
63
  ```
53
64
 
54
65
  #### get a single file; returns a GithubContentFile
55
- ```
66
+
67
+ ```py
56
68
  repo.get_contents("osgithub/__init__.py", "main")
57
69
  ```
58
70
 
59
71
  #### get_commits_for_file(self, path, ref, number_of_commits=1):
72
+
60
73
  Returns a list of commits, just the last one by default
61
- ```
74
+
75
+ ```py
62
76
  repo.get_commits_for_file("osgithub", "main")
63
77
  ```
78
+
64
79
  To return more commits:
65
- ```
80
+
81
+ ```py
66
82
  repo.get_commits_for_file("osgithub", "main", number_of_commits=10)
67
83
  ```
68
84
 
69
85
  Get details for a single commit by sha - returns author and date of the commit
70
- ```
86
+
87
+ ```py
71
88
  repo.get_commit("7a6f60e8e74b9c93a9c6322b3151ee437fa4be61")
72
89
  ```
73
90
 
74
-
75
91
  #### Repo information
76
92
 
77
93
  Fetch the HTML from the README.md of repo:
78
- ```
94
+
95
+ ```py
79
96
  repo.get_readme(tag="main")
80
97
  ```
81
98
 
82
99
  Fetch the About and Name of the repo:
83
- ```
100
+
101
+ ```py
84
102
  repo.get_details()
85
103
  ```
86
104
 
87
105
  Fetch tags with name and sha:
88
- ```
106
+
107
+ ```py
89
108
  repo.get_tags()
90
109
  ```
91
110
 
92
-
93
111
  ### Caching
112
+
94
113
  Requests can optionally be cached, using a sqlite backend.
95
114
 
96
115
  To use caching:
97
- ```
116
+
117
+ ```py
98
118
  client = GithubClient(use_cache=True)
99
119
  ```
100
120
 
101
121
  Set a global expiry for the session (never expires by default):
102
- ```
122
+
123
+ ```py
103
124
  # expire all cached requests after 300s
104
125
  client = GithubClient(expire_after=300)
105
126
  ```
106
127
 
107
128
  Set expiry on specific url patterns (falls back to `expire_after` if no match found)
108
- ```
129
+
130
+ ```py
109
131
  urls_expire_after = {
110
132
  '*/pulls': 60, # expire requests to get pull requests after 60 secs
111
133
  '*/branches': 60 * 5, # expire requests to get branches after 5 mins
@@ -115,7 +137,8 @@ client = GithubClient(urls_expire_after=urls_expire_after)
115
137
  ```
116
138
 
117
139
  #### Clear the cache for this repo
118
- ```
140
+
141
+ ```py
119
142
  repo.clear_cache()
120
143
  ```
121
144
 
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: osgithub
3
+ Version: 0.6.0
4
+ Home-page: https://github.com/opensafely-core/osgithub
5
+ Author: OpenSAFELY
6
+ Author-email: tech@opensafely.org
7
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
8
+ Requires-Python: >=3.9
9
+ License-File: LICENSE
10
+ Requires-Dist: requests
11
+ Requires-Dist: requests-cache
12
+ Requires-Dist: furl
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: classifier
16
+ Dynamic: home-page
17
+ Dynamic: license-file
18
+ Dynamic: requires-dist
19
+ Dynamic: requires-python
@@ -8,4 +8,5 @@ osgithub.egg-info/PKG-INFO
8
8
  osgithub.egg-info/SOURCES.txt
9
9
  osgithub.egg-info/dependency_links.txt
10
10
  osgithub.egg-info/requires.txt
11
- osgithub.egg-info/top_level.txt
11
+ osgithub.egg-info/top_level.txt
12
+ tests/test_github.py
@@ -0,0 +1,737 @@
1
+ import json
2
+ from base64 import b64encode
3
+ from datetime import datetime, timezone
4
+ from os import environ
5
+
6
+ import pytest
7
+ from furl import furl
8
+ from requests.exceptions import HTTPError
9
+
10
+ from osgithub import GithubAPIException, GithubClient, GithubRepo
11
+
12
+ from .conftest import remove_cache_file_if_exists
13
+
14
+
15
+ def register_uri(httpretty, path, queryparams=None, status=200, body=None):
16
+ url = furl("https://api.github.com")
17
+ url.path.segments += [*path.split("/")]
18
+ if queryparams:
19
+ url.add(queryparams)
20
+ httpretty.register_uri(
21
+ httpretty.GET,
22
+ url.url,
23
+ status=status,
24
+ body=json.dumps(body or ""),
25
+ match_querystring=True,
26
+ )
27
+
28
+
29
+ def test_github_client_get_repo(httpretty):
30
+ # Mock the github request
31
+ register_uri(httpretty, "repos/test/foo", body={"name": "foo", "description": ""})
32
+ client = GithubClient()
33
+ repo = client.get_repo("test", "foo")
34
+ assert repo.repo_path_segments == ["repos", "test", "foo"]
35
+
36
+
37
+ def test_github_client_token(reset_environment_after_test):
38
+ """Authorization headers is set based on environment variable"""
39
+ environ["GITHUB_TOKEN"] = "test"
40
+ client = GithubClient()
41
+ assert client.headers["Authorization"] == "token test"
42
+
43
+ del environ["GITHUB_TOKEN"]
44
+ client = GithubClient()
45
+ assert "Authorization" not in client.headers
46
+
47
+
48
+ def test_github_repo_get_url():
49
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
50
+ assert repo._url is None
51
+ assert repo.url == "https://github.com/test/foo"
52
+ assert repo._url == repo.url
53
+
54
+
55
+ def test_github_client_get_repo_not_found(httpretty):
56
+ # Mock the github request
57
+ register_uri(httpretty, "repos/test/bar", status=404, body={"message": "Not found"})
58
+ client = GithubClient()
59
+ with pytest.raises(GithubAPIException, match="Not found"):
60
+ client.get_repo("test", "bar")
61
+
62
+
63
+ @pytest.mark.parametrize("use_cache", [True, False])
64
+ def test_github_client_get_repo_with_cache(httpretty, use_cache):
65
+ client = GithubClient(use_cache=use_cache)
66
+
67
+ # set up mock request with valid response and call it
68
+ register_uri(
69
+ httpretty, "repos/test/test-cache", body={"name": "foo", "description": ""}
70
+ )
71
+ client.get_repo("test", "test-cache")
72
+
73
+ # re-mock the repos request to a 404, should raise an exception if called directly
74
+ register_uri(
75
+ httpretty, "repos/test/test-cache", status=404, body={"message": "Not found"}
76
+ )
77
+
78
+ if use_cache:
79
+ # No exception raised because the first response was cached
80
+ client.get_repo("test", "test-cache")
81
+ else:
82
+ # Exception raised because the repos endpoint was fetched again
83
+ with pytest.raises(GithubAPIException, match="Not found"):
84
+ client.get_repo("test", "test-cache")
85
+
86
+
87
+ @pytest.mark.parametrize("state", ["open", "closed"])
88
+ def test_github_repo_get_pull_requests(httpretty, state):
89
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
90
+ pull_requests = {
91
+ "open": [
92
+ {
93
+ "url": "https://api.github.com/repos/test/foo/pulls/1",
94
+ "id": 1,
95
+ "state": "open",
96
+ "title": "Open PR",
97
+ "user": {"login": "testuser", "id": 123},
98
+ "body": "",
99
+ "created_at": "2021-08-16T04:11:31Z",
100
+ "updated_at": None,
101
+ "closed_at": None,
102
+ "merged_at": None,
103
+ }
104
+ ],
105
+ "closed": [
106
+ {
107
+ "url": "https://api.github.com/repos/test/foo/pulls/2",
108
+ "id": 2,
109
+ "state": "closed",
110
+ "title": "Closed PR",
111
+ "user": {"login": "testuser", "id": 123},
112
+ "body": "",
113
+ "created_at": "2021-08-01T10:00:00Z",
114
+ "updated_at": None,
115
+ "closed_at": "2021-08-12T09:11:00Z",
116
+ "merged_at": None,
117
+ }
118
+ ],
119
+ }
120
+ # Mock the github requests
121
+ register_uri(
122
+ httpretty,
123
+ "repos/test/foo/pulls",
124
+ queryparams=dict(state=state, page=1, per_page=30),
125
+ body=pull_requests[state],
126
+ )
127
+ pulls = repo.get_pull_requests(state)
128
+ assert pulls == pull_requests[state]
129
+
130
+
131
+ def test_github_repo_get_open_pull_request_count(httpretty):
132
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
133
+ pull_requests = [
134
+ {
135
+ "url": "https://api.github.com/repos/test/foo/pulls/1",
136
+ "id": 1,
137
+ "state": "open",
138
+ },
139
+ {
140
+ "url": "https://api.github.com/repos/test/foo/pulls/2",
141
+ "id": 2,
142
+ "state": "open",
143
+ },
144
+ ]
145
+ # Mock the github requests
146
+ register_uri(
147
+ httpretty,
148
+ "repos/test/foo/pulls",
149
+ queryparams=dict(state="open", page=1, per_page=30),
150
+ body=pull_requests,
151
+ )
152
+ assert repo.open_pull_request_count == 2
153
+
154
+
155
+ def test_github_repo_get_branches(httpretty):
156
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
157
+ sha1 = "1" * 40
158
+ sha2 = "2" * 40
159
+ branches = [
160
+ {
161
+ "name": "test_branch",
162
+ "commit": {
163
+ "sha": sha1,
164
+ "url": f"https://api.github.com/repos/test/foo/commits/{sha1}",
165
+ },
166
+ "protected": False,
167
+ },
168
+ {
169
+ "name": "test_branch1",
170
+ "commit": {
171
+ "sha": sha2,
172
+ "url": f"https://api.github.com/repos/test/foo/commits/{sha2}",
173
+ },
174
+ "protected": False,
175
+ },
176
+ ]
177
+ # Mock the github requests
178
+ register_uri(httpretty, "repos/test/foo/branches", body=branches)
179
+ assert repo.get_branches() == branches
180
+ assert repo.branch_count == 2
181
+
182
+
183
+ def test_github_repo_get_multipage_pull_request_count(httpretty):
184
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
185
+ for i in range(1, 3):
186
+ pull_requests = [
187
+ {
188
+ "url": f"https://api.github.com/repos/test/foo/pulls/{pr_num * i}",
189
+ "id": pr_num * i,
190
+ "state": "open",
191
+ }
192
+ for pr_num in range(1, 31)
193
+ ]
194
+ # Mock the github request
195
+ register_uri(
196
+ httpretty,
197
+ "repos/test/foo/pulls",
198
+ queryparams=dict(state="open", page=i, per_page=30),
199
+ body=pull_requests,
200
+ )
201
+ last_page_pull_requests = [
202
+ {
203
+ "url": f"https://api.github.com/repos/test/foo/pulls/{pr_num * 3}",
204
+ "id": pr_num * 3,
205
+ "state": "open",
206
+ }
207
+ for pr_num in range(1, 11)
208
+ ]
209
+ register_uri(
210
+ httpretty,
211
+ "repos/test/foo/pulls",
212
+ queryparams=dict(state="open", page=3, per_page=30),
213
+ body=last_page_pull_requests,
214
+ )
215
+
216
+ assert repo.open_pull_request_count == 70
217
+
218
+
219
+ def test_github_repo_get_contents_single_file(httpretty):
220
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
221
+ str_content = """
222
+ <html>
223
+ <head>
224
+ <style type="text/css">body {margin: 0;}</style>
225
+ <style type="text/css">a {background-color: red;}</style>
226
+ <script src="https://a-js-package.js"></script>
227
+ </head>
228
+ <body><p>foo</p></body>
229
+ </html>
230
+ """
231
+ # Content retrieved from GitHub is base64-encoded, decoded to str for json
232
+ b64_content = b64encode(bytes(str_content, encoding="utf-8")).decode()
233
+ # Mock the github request
234
+ reponse_json = {
235
+ "name": "test-file.html",
236
+ "path": "test-folder/test-file.html",
237
+ "sha": "abcd1234",
238
+ "size": 1234,
239
+ "encoding": "base64",
240
+ "content": b64_content,
241
+ }
242
+ register_uri(
243
+ httpretty,
244
+ "repos/test/foo/contents/test-folder/test-file.html",
245
+ queryparams=dict(ref="main"),
246
+ body=reponse_json,
247
+ )
248
+
249
+ commits_response = [{"commit": {"committer": {"date": "2021-03-01T10:00:00Z"}}}]
250
+ register_uri(
251
+ httpretty,
252
+ "repos/test/foo/commits",
253
+ queryparams=dict(sha="main", path="test-folder/test-file.html", per_page=1),
254
+ body=commits_response,
255
+ )
256
+
257
+ content_file = repo.get_contents("test-folder/test-file.html", ref="main")
258
+ assert content_file.name == "test-file.html"
259
+ # decoded content retrieves the original str contents
260
+ assert content_file.decoded_content == str_content
261
+
262
+ # get contents with content fetch type
263
+ content_file, fetch_type = repo.get_contents(
264
+ "test-folder/test-file.html", ref="main", return_fetch_type=True
265
+ )
266
+ assert content_file.name == "test-file.html"
267
+ assert fetch_type == "contents"
268
+
269
+
270
+ def test_github_repo_get_last_updated(httpretty):
271
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
272
+ commit_dates = [
273
+ "2021-03-01T10:00:00Z",
274
+ "2021-02-14T10:00:00Z",
275
+ "2021-02-01T10:00:00Z",
276
+ ]
277
+ commits_response = [
278
+ {"commit": {"committer": {"date": commit_date}}} for commit_date in commit_dates
279
+ ]
280
+ register_uri(
281
+ httpretty,
282
+ "repos/test/foo/commits",
283
+ queryparams=dict(sha="main", path="test-folder/test-file.html", per_page=1),
284
+ body=commits_response,
285
+ )
286
+
287
+ last_updated = repo.get_last_updated(path="test-folder/test-file.html", ref="main")
288
+ assert last_updated == datetime(2021, 3, 1, 10, 0, 0, tzinfo=timezone.utc)
289
+
290
+
291
+ @pytest.mark.parametrize(
292
+ "status_code,body,expected_exception,expected_match",
293
+ [
294
+ (404, {"message": "Not found"}, GithubAPIException, "Not found"),
295
+ (
296
+ 403,
297
+ {"errors": [{"code": "other_code", "message": "An unexpected 403"}]},
298
+ HTTPError,
299
+ "Forbidden for url",
300
+ ),
301
+ (
302
+ 401,
303
+ {"errors": [{"code": "other_code", "message": "An unexpected 403"}]},
304
+ HTTPError,
305
+ "Unauthorized for url",
306
+ ),
307
+ (
308
+ 403,
309
+ {
310
+ "unknown": [
311
+ {"code": "other_code", "message": "A 403 without an 'errors' key"}
312
+ ]
313
+ },
314
+ GithubAPIException,
315
+ "A 403 without an 'errors' key",
316
+ ),
317
+ ],
318
+ )
319
+ def test_github_repo_get_contents_exceptions(
320
+ httpretty, status_code, body, expected_exception, expected_match
321
+ ):
322
+ """
323
+ Test expected and unexpected exceptions from get_contents
324
+ """
325
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
326
+ # Mock the github request
327
+ register_uri(
328
+ httpretty,
329
+ "repos/test/foo/contents/test-folder/test-file.html",
330
+ queryparams=dict(ref="main"),
331
+ status=status_code,
332
+ body=body,
333
+ )
334
+ with pytest.raises(expected_exception, match=expected_match):
335
+ repo.get_contents("test-folder/test-file.html", ref="main")
336
+
337
+
338
+ @pytest.mark.parametrize(
339
+ "filepath,expected_filename",
340
+ [
341
+ ("test-folder/test-file.html", "test-file.html"),
342
+ ("test-folder/test-file1.html", "test-file1.html"),
343
+ ("test-folder/test-file2.html", None),
344
+ ],
345
+ )
346
+ def test_github_repo_matching_file_from_parent_contents(
347
+ httpretty, filepath, expected_filename
348
+ ):
349
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
350
+ # Mock the github requests
351
+ # get the parent folder contents
352
+ response_json = [
353
+ {
354
+ "name": "test-file.html",
355
+ "path": "test-folder/test-file.html",
356
+ "sha": "abcd1234",
357
+ "size": 1234,
358
+ "encoding": "base64",
359
+ },
360
+ {
361
+ "name": "test-file1.html",
362
+ "path": "test-folder/test-file1.html",
363
+ "sha": "abcd2345",
364
+ "size": 1234,
365
+ "encoding": "base64",
366
+ },
367
+ ]
368
+ register_uri(
369
+ httpretty,
370
+ "repos/test/foo/contents/test-folder",
371
+ queryparams=dict(ref="main"),
372
+ body=response_json,
373
+ )
374
+
375
+ matching_file = repo.get_matching_file_from_parent_contents(filepath, "main")
376
+ if expected_filename is None:
377
+ assert matching_file is None
378
+ else:
379
+ assert matching_file.name == expected_filename
380
+
381
+
382
+ def test_github_repo_get_contents_folder(httpretty):
383
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
384
+ # Mock the github request
385
+ response_json = [
386
+ {
387
+ "name": "test-file1.html",
388
+ "path": "test-folder/test-file1.html",
389
+ "sha": "abcd1234",
390
+ "size": 1234,
391
+ "encoding": "base64",
392
+ },
393
+ {
394
+ "name": "test-file2.html",
395
+ "path": "test-folder/test-file2.html",
396
+ "sha": "abcd5678",
397
+ "size": 1234,
398
+ "encoding": "base64",
399
+ },
400
+ ]
401
+ register_uri(
402
+ httpretty,
403
+ "repos/test/foo/contents/test-folder",
404
+ queryparams=dict(ref="main"),
405
+ body=response_json,
406
+ )
407
+ contents = repo.get_contents("test-folder", ref="main")
408
+ assert isinstance(contents, list)
409
+ assert len(contents) == 2
410
+ assert contents[0].name == "test-file1.html"
411
+ assert contents[1].name == "test-file2.html"
412
+ # decoded content returns None when the ContntFile was generated from a list of files
413
+ # returned from github
414
+ assert contents[0].decoded_content is None
415
+ assert contents[1].decoded_content is None
416
+
417
+
418
+ def test_github_repo_get_contents_from_git_blob(httpretty):
419
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
420
+ str_content = """
421
+ <html>
422
+ <head>
423
+ <style type="text/css">body {margin: 0;}</style>
424
+ <style type="text/css">a {background-color: red;}</style>
425
+ <script src="https://a-js-package.js"></script>
426
+ </head>
427
+ <body><p>foo</p></body>
428
+ </html>
429
+ """
430
+ # Content retrieved from GitHub is base64-encoded, decoded to str for json
431
+ b64_content = b64encode(bytes(str_content, encoding="utf-8")).decode()
432
+ # Mock the github requests
433
+ # get the parent folder contents
434
+ response_json = [
435
+ {
436
+ "name": "test-file.html",
437
+ "path": "test-folder/test-file.html",
438
+ "sha": "abcd1234",
439
+ "size": 1234,
440
+ "encoding": "base64",
441
+ },
442
+ ]
443
+ register_uri(
444
+ httpretty,
445
+ "repos/test/foo/contents/test-folder",
446
+ queryparams=dict(ref="main"),
447
+ body=response_json,
448
+ )
449
+
450
+ # get the git blob
451
+ response_json = {
452
+ "name": "test-file.html",
453
+ "path": "test-folder/test-file.html",
454
+ "sha": "abcd1234",
455
+ "size": 1234,
456
+ "encoding": "base64",
457
+ "content": b64_content,
458
+ }
459
+ register_uri(httpretty, "repos/test/foo/git/blobs/abcd1234", body=response_json)
460
+
461
+ # get the commits for last updated
462
+ commits_response = [{"commit": {"committer": {"date": "2021-03-01T10:00:00Z"}}}]
463
+ register_uri(
464
+ httpretty,
465
+ "repos/test/foo/commits",
466
+ queryparams=dict(sha="main", path="test-folder/test-file.html", per_page=1),
467
+ body=commits_response,
468
+ )
469
+
470
+ content_file = repo.get_contents(
471
+ "test-folder/test-file.html", "main", from_git_blob=True
472
+ )
473
+ assert content_file.name == "test-file.html"
474
+ # decoded content retrieves the original str contents
475
+ assert content_file.decoded_content == str_content
476
+
477
+
478
+ def test_github_repo_get_contents_too_large_file(httpretty):
479
+ """
480
+ Test get_contents with a too-large file resorts to fetching content from the git blob
481
+ """
482
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
483
+ str_content = """
484
+ <html>
485
+ <head>
486
+ <style type="text/css">body {margin: 0;}</style>
487
+ <style type="text/css">a {background-color: red;}</style>
488
+ <script src="https://a-js-package.js"></script>
489
+ </head>
490
+ <body><p>foo</p></body>
491
+ </html>
492
+ """
493
+ # Content retrieved from GitHub is base64-encoded, decoded to str for json
494
+ b64_content = b64encode(bytes(str_content, encoding="utf-8")).decode()
495
+
496
+ # Mock the github requests
497
+ # First tries the contents endpoint and gets a 403
498
+ register_uri(
499
+ httpretty,
500
+ "repos/test/foo/contents/test-folder/test-file.html",
501
+ status=200,
502
+ queryparams=dict(ref="main"),
503
+ body={
504
+ "name": "test-file.html",
505
+ "path": "test-folder/test-file.html",
506
+ "sha": "abcd1234",
507
+ "size": 1234,
508
+ "encoding": "base64",
509
+ "content": "",
510
+ },
511
+ )
512
+
513
+ # gets the parent folder contents
514
+ register_uri(
515
+ httpretty,
516
+ "repos/test/foo/contents/test-folder",
517
+ status=200,
518
+ queryparams=dict(ref="main"),
519
+ body=[
520
+ {
521
+ "name": "test-file.html",
522
+ "path": "test-folder/test-file.html",
523
+ "sha": "abcd1234",
524
+ "size": 1234,
525
+ "encoding": "base64",
526
+ },
527
+ ],
528
+ )
529
+
530
+ # then gets the git blob
531
+ register_uri(
532
+ httpretty,
533
+ "repos/test/foo/git/blobs/abcd1234",
534
+ status=200,
535
+ body={
536
+ "name": "test-file.html",
537
+ "path": "test-folder/test-file.html",
538
+ "sha": "abcd1234",
539
+ "size": 1234,
540
+ "encoding": "base64",
541
+ "content": b64_content,
542
+ },
543
+ )
544
+
545
+ # get the commits for last updated
546
+ commits_response = [{"commit": {"committer": {"date": "2021-03-01T10:00:00Z"}}}]
547
+ register_uri(
548
+ httpretty,
549
+ "repos/test/foo/commits",
550
+ queryparams=dict(sha="main", path="test-folder/test-file.html", per_page=1),
551
+ body=commits_response,
552
+ )
553
+
554
+ content_file = repo.get_contents("test-folder/test-file.html", ref="main")
555
+ assert content_file.decoded_content == str_content
556
+ assert content_file.last_updated == datetime(
557
+ 2021, 3, 1, 10, 0, 0, tzinfo=timezone.utc
558
+ )
559
+
560
+
561
+ def test_github_repo_get_readme(httpretty):
562
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
563
+ readme_content = b"<div id='readme'><h1>Foo</h1><p>A README.</p></div>"
564
+ httpretty.register_uri(
565
+ httpretty.GET,
566
+ "https://api.github.com/repos/test/foo/readme?ref=main",
567
+ status=200,
568
+ body=readme_content,
569
+ match_querystring=True,
570
+ )
571
+ readme_content = repo.get_readme(tag="main")
572
+ assert readme_content == "<div id='readme'><h1>Foo</h1><p>A README.</p></div>"
573
+
574
+
575
+ def test_github_repo_details(httpretty):
576
+ register_uri(
577
+ httpretty,
578
+ "repos/test/foo",
579
+ status=200,
580
+ body={"name": "foo", "description": "A test repo"},
581
+ )
582
+ # If the repo is instantiated with an "about", the endpoint isn't called
583
+ repo = GithubRepo(
584
+ client=GithubClient(use_cache=False),
585
+ owner="test",
586
+ name="foo",
587
+ about="A different description",
588
+ )
589
+ details = repo.get_repo_details()
590
+ assert details == {"name": "foo", "about": "A different description"}
591
+ assert httpretty.latest_requests() == []
592
+
593
+ # instantiate with no "about" arg, need to fetch it for the details
594
+ repo1 = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
595
+ details = repo1.get_repo_details()
596
+ assert details == {"name": "foo", "about": "A test repo"}
597
+ latest_requests = httpretty.latest_requests()
598
+ assert len(latest_requests) == 1
599
+ assert latest_requests[0].url == "https://api.github.com/repos/test/foo"
600
+
601
+
602
+ def test_github_repo_get_tags(httpretty):
603
+ sha1 = "1" * 40
604
+ sha2 = "2" * 40
605
+ tags = [
606
+ {"name": "v1.0", "commit": {"sha": sha1}},
607
+ {"name": "v2.0", "commit": {"sha": sha2}},
608
+ ]
609
+ register_uri(httpretty, "repos/test/foo/tags", status=200, body=tags)
610
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
611
+ assert repo.get_tags() == [
612
+ {"tag_name": "v1.0", "sha": sha1},
613
+ {"tag_name": "v2.0", "sha": sha2},
614
+ ]
615
+
616
+
617
+ def test_github_repo_get_commit(httpretty):
618
+ sha = "1" * 40
619
+ commit_body = {
620
+ "author": {"name": "Donald Duck"},
621
+ "committer": {"date": "2021-03-01T10:00:00Z"},
622
+ }
623
+ register_uri(
624
+ httpretty, f"repos/test/foo/git/commits/{sha}", status=200, body=commit_body
625
+ )
626
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
627
+ assert repo.get_commit(sha) == {
628
+ "author": "Donald Duck",
629
+ "date": "2021-03-01T10:00:00Z",
630
+ }
631
+
632
+
633
+ def test_github_repo_get_contributors(httpretty):
634
+ contribs = [{"login": "octocat"}, {"login": "octodog"}]
635
+ register_uri(httpretty, "repos/test/foo/contributors", status=200, body=contribs)
636
+ repo = GithubRepo(client=GithubClient(use_cache=False), owner="test", name="foo")
637
+ assert repo.get_contributors() == ["octocat", "octodog"]
638
+
639
+
640
+ @pytest.mark.parametrize(
641
+ "use_cache,num_requests",
642
+ [
643
+ (True, 1),
644
+ (False, 2),
645
+ ],
646
+ )
647
+ def test_github_repo_get_topics(httpretty, use_cache, num_requests):
648
+ register_uri(
649
+ httpretty,
650
+ "repos/test/foo",
651
+ status=200,
652
+ body={"name": "foo", "description": "a description", "topics": ["bar", "baz"]},
653
+ )
654
+ repo = GithubClient(use_cache=use_cache).get_repo("test", "foo")
655
+ assert repo.get_topics() == ["bar", "baz"]
656
+ assert len(httpretty.latest_requests()) == num_requests
657
+
658
+
659
+ def test_clear_cache(httpretty, reset_environment_after_test):
660
+ # mock the requests
661
+ register_uri(httpretty, "repos/test/foo", body={"name": "foo", "description": ""})
662
+ httpretty.register_uri(httpretty.GET, "https://www.test.com/", status=200)
663
+
664
+ # make sure we start with a fresh cache
665
+ remove_cache_file_if_exists()
666
+ client = GithubClient(use_cache=True)
667
+ # no github requests have been made, so cache is currently clear
668
+ assert client.session.cache.urls() == []
669
+
670
+ # A real repo
671
+ repo = client.get_repo("test", "foo")
672
+
673
+ # 1 call made, to get contents
674
+ assert len(client.session.cache.urls()) == 1
675
+ # make another request using this cache session
676
+ client.session.get("https://www.test.com/")
677
+ assert len(client.session.cache.urls()) == 2
678
+ # Clearing the cache only clears urls related to this report
679
+ repo.clear_cache()
680
+ assert client.session.cache.urls() == ["https://www.test.com/"]
681
+
682
+
683
+ @pytest.mark.integration
684
+ def test_integration(reset_environment_after_test):
685
+ """Test repo methods with a real github repo"""
686
+ # make sure we start with a fresh cache
687
+ remove_cache_file_if_exists()
688
+ client = GithubClient(use_cache=True)
689
+ # Set up a real repo
690
+ repo = client.get_repo("opensafely", "output-explorer-test-repo")
691
+
692
+ # Fetch a known folder
693
+ contents = repo.get_contents("test-outputs", ref="master")
694
+ assert len(contents) == 4
695
+ assert sorted([contentfile.name for contentfile in contents]) == [
696
+ "output.html",
697
+ "sro-measures.html",
698
+ "vaccine-coverage-new.html",
699
+ "vaccine-coverage-original.html",
700
+ ]
701
+
702
+ # Fetch a file
703
+ contents, fetch_type = repo.get_contents(
704
+ "test-outputs/output.html", ref="master", return_fetch_type=True
705
+ )
706
+ assert contents.name == "output.html"
707
+ assert fetch_type == "contents"
708
+
709
+ # Fetch a non-existent file
710
+ with pytest.raises(GithubAPIException):
711
+ repo.get_contents("test-outputs/output-unknown.html", ref="master")
712
+
713
+ # Fetch a non-existent branch
714
+ with pytest.raises(GithubAPIException):
715
+ repo.get_contents("test-outputs/output.html", ref="foo")
716
+
717
+ # Fetch README
718
+ readme = repo.get_readme(tag="master")
719
+ assert readme.startswith("<div")
720
+ assert "This is a test repo for use by output-explorer's tests." in readme
721
+
722
+ # Fetch details
723
+ details = repo.get_repo_details()
724
+ assert details == {
725
+ "name": "output-explorer-test-repo",
726
+ "about": "A test repo for output-explorer's tests",
727
+ }
728
+
729
+ # Fetch tags
730
+ tagged_sha = "7a6f60e8e74b9c93a9c6322b3151ee437fa4be61"
731
+ tags = repo.get_tags()
732
+ assert len(tags) >= 1
733
+ assert {"tag_name": "test-tag", "sha": tagged_sha} in tags
734
+
735
+ # get commit details
736
+ commit = repo.get_commit(sha=tagged_sha)
737
+ assert commit == {"author": "Ben Butler-Cole", "date": "2021-06-02T10:52:37Z"}
osgithub-0.5.0/PKG-INFO DELETED
@@ -1,15 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: osgithub
3
- Version: 0.5.0
4
- Summary: UNKNOWN
5
- Home-page: https://github.com/opensafely-core/osgithub
6
- Author: OpenSAFELY
7
- Author-email: tech@opensafely.org
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
11
- Requires-Python: >=3.9
12
- License-File: LICENSE
13
-
14
- UNKNOWN
15
-
@@ -1,15 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: osgithub
3
- Version: 0.5.0
4
- Summary: UNKNOWN
5
- Home-page: https://github.com/opensafely-core/osgithub
6
- Author: OpenSAFELY
7
- Author-email: tech@opensafely.org
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
11
- Requires-Python: >=3.9
12
- License-File: LICENSE
13
-
14
- UNKNOWN
15
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes