|
| 1 | +"""Utilities for creating and retrieving GitHub gists.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import os |
| 6 | +from typing import Any |
| 7 | + |
| 8 | +import requests |
| 9 | + |
| 10 | +BASE_URL = "https://api.github.com" |
| 11 | +CREATE_GIST_ENDPOINT = f"{BASE_URL}/gists" |
| 12 | + |
| 13 | +# GitHub recommends using personal access tokens to authenticate requests. |
| 14 | +# The tests patch the network calls so the token can be left empty when running |
| 15 | +# locally, but an actual token will be required when using the module for real |
| 16 | +# interactions with the GitHub API. |
| 17 | +GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "") |
| 18 | + |
| 19 | + |
| 20 | +class GitHubAPIError(RuntimeError): |
| 21 | + """Raised when the GitHub API returns an error response.""" |
| 22 | + |
| 23 | + |
| 24 | +def _build_headers(auth_token: str | None) -> dict[str, str]: |
| 25 | + headers: dict[str, str] = {"Accept": "application/vnd.github.v3+json"} |
| 26 | + if auth_token: |
| 27 | + headers["Authorization"] = f"token {auth_token}" |
| 28 | + return headers |
| 29 | + |
| 30 | + |
| 31 | +def create_gist( |
| 32 | + description: str, |
| 33 | + files: dict[str, dict[str, str]], |
| 34 | + *, |
| 35 | + public: bool = True, |
| 36 | + auth_token: str | None = None, |
| 37 | +) -> dict[str, Any]: |
| 38 | + """Create a GitHub gist. |
| 39 | +
|
| 40 | + Parameters |
| 41 | + ---------- |
| 42 | + description: |
| 43 | + A short description that appears above the gist files. |
| 44 | + files: |
| 45 | + Mapping of file names to dictionaries containing a ``content`` key with |
| 46 | + the file contents. |
| 47 | + public: |
| 48 | + Set to ``True`` to create a public gist, otherwise the gist will be |
| 49 | + secret. |
| 50 | + auth_token: |
| 51 | + Optional personal access token. If not supplied, the function will fall |
| 52 | + back to the value of ``GITHUB_TOKEN`` from the environment. |
| 53 | +
|
| 54 | + Returns |
| 55 | + ------- |
| 56 | + dict |
| 57 | + The JSON response from the GitHub API describing the newly created |
| 58 | + gist. |
| 59 | +
|
| 60 | + Raises |
| 61 | + ------ |
| 62 | + GitHubAPIError |
| 63 | + If the API returns a non-success status code. |
| 64 | + """ |
| 65 | + |
| 66 | + token = auth_token if auth_token is not None else GITHUB_TOKEN |
| 67 | + headers = _build_headers(token) |
| 68 | + payload = {"description": description, "public": public, "files": files} |
| 69 | + |
| 70 | + response = requests.post(CREATE_GIST_ENDPOINT, json=payload, headers=headers, timeout=10) |
| 71 | + if response.status_code >= 400: |
| 72 | + raise GitHubAPIError(response.text) |
| 73 | + return response.json() |
| 74 | + |
| 75 | + |
| 76 | +def get_gist(gist_id: str, auth_token: str | None = None) -> dict[str, Any]: |
| 77 | + """Retrieve a gist by its identifier. |
| 78 | +
|
| 79 | + Parameters |
| 80 | + ---------- |
| 81 | + gist_id: |
| 82 | + Unique identifier of the gist returned by :func:`create_gist`. |
| 83 | + auth_token: |
| 84 | + Optional personal access token. If omitted the ``GITHUB_TOKEN`` |
| 85 | + environment variable will be used. |
| 86 | +
|
| 87 | + Returns |
| 88 | + ------- |
| 89 | + dict |
| 90 | + Parsed JSON describing the gist. |
| 91 | +
|
| 92 | + Raises |
| 93 | + ------ |
| 94 | + GitHubAPIError |
| 95 | + If the API returns a non-success status code. |
| 96 | + """ |
| 97 | + |
| 98 | + token = auth_token if auth_token is not None else GITHUB_TOKEN |
| 99 | + headers = _build_headers(token) |
| 100 | + endpoint = f"{CREATE_GIST_ENDPOINT}/{gist_id}" |
| 101 | + response = requests.get(endpoint, headers=headers, timeout=10) |
| 102 | + if response.status_code >= 400: |
| 103 | + raise GitHubAPIError(response.text) |
| 104 | + return response.json() |
| 105 | + |
| 106 | + |
| 107 | +if __name__ == "__main__": # pragma: no cover |
| 108 | + if not GITHUB_TOKEN: |
| 109 | + raise ValueError("'GITHUB_TOKEN' field cannot be empty.") |
| 110 | + |
| 111 | + example_files = {"file1.txt": {"content": "Aren't gists great!"}} |
| 112 | + gist_data = create_gist("Example gist created from Python", example_files) |
| 113 | + gist_id = gist_data.get("id", "") |
| 114 | + if gist_id: |
| 115 | + retrieved = get_gist(gist_id) |
| 116 | + print(f"Created gist {gist_id} containing {len(retrieved.get('files', {}))} file(s).") |
| 117 | + else: |
| 118 | + print("Failed to create gist.") |
0 commit comments