Skip to content

GitHub Profile Viewer

The GitHub REST API is a fantastic teaching API — no key required for basic reads, clean JSON, and data everyone recognizes. In this tutorial you build a Tkinter app that takes a username and shows the user’s name, bio, repo count, and followers. Then you grow it into a real profile dashboard: avatar images, a scrollable repository list sorted by stars, authenticated requests for higher rate limits, and a side-by-side comparison of two developers.

You will leave understanding:

  • How to call a public REST API and read its JSON response.
  • Why data.get("name", "N/A") is safer than data["name"].
  • How API rate limits work and how authentication raises them.
  • The fetch → display pattern that scales to any JSON API.
  • Python 3.6 or above.
  • A text editor or IDE.
  • The requests library: pip install requests.
  • Tkinter (bundled with Python).
  • Optional: a GitHub Personal Access Token for higher rate limits.
  1. Create a folder named github-profile-viewer.
  2. Inside it, create github_profile_viewer.py.
  3. Install the dependency: pip install requests.
github_profile_viewer.py pch.viewSource
github_profile_viewer.py
"""
GitHub Profile Viewer

A Python application that fetches and displays GitHub user profile information using the GitHub API.
Features include:
- Fetching user details like name, bio, repositories, followers, etc.
- Displaying the information in a user-friendly format.
"""

import requests
from tkinter import Tk, Label, Entry, Button, Text, END, messagebox


class GitHubProfileViewer:
    def __init__(self, root):
        self.root = root
        self.root.title("GitHub Profile Viewer")

        Label(root, text="Enter GitHub Username:").grid(row=0, column=0, padx=10, pady=10)
        self.username_entry = Entry(root, width=30)
        self.username_entry.grid(row=0, column=1, padx=10, pady=10)

        Button(root, text="Fetch Profile", command=self.fetch_profile).grid(row=1, column=0, columnspan=2, pady=10)

        self.result_text = Text(root, width=50, height=20)
        self.result_text.grid(row=2, column=0, columnspan=2, padx=10, pady=10)

    def fetch_profile(self):
        """Fetch GitHub profile information for the entered username."""
        username = self.username_entry.get()
        if not username:
            messagebox.showerror("Error", "Please enter a GitHub username.")
            return

        url = f"https://api.github.com/users/{username}"
        response = requests.get(url)

        if response.status_code == 200:
            data = response.json()
            self.display_profile(data)
        else:
            messagebox.showerror("Error", f"Failed to fetch profile. Status code: {response.status_code}")

    def display_profile(self, data):
        """Display the fetched profile information in the text widget."""
        self.result_text.delete(1.0, END)
        self.result_text.insert(END, f"Name: {data.get('name', 'N/A')}\n")
        self.result_text.insert(END, f"Bio: {data.get('bio', 'N/A')}\n")
        self.result_text.insert(END, f"Public Repos: {data.get('public_repos', 'N/A')}\n")
        self.result_text.insert(END, f"Followers: {data.get('followers', 'N/A')}\n")
        self.result_text.insert(END, f"Following: {data.get('following', 'N/A')}\n")
        self.result_text.insert(END, f"Profile URL: {data.get('html_url', 'N/A')}\n")


def main():
    root = Tk()
    app = GitHubProfileViewer(root)
    root.mainloop()


if __name__ == "__main__":
    main()
command
C:\Users\Your Name\github-profile-viewer> python github_profile_viewer.py
# Type a username (e.g. "torvalds") and click "Fetch Profile".

Read from the top: this is what runs when you execute the file, and which function calls which. It is generated from the code, so it cannot drift from it.

diagram Diagram mermaid
github_profile_viewer.py
url = f"https://api.github.com/users/{username}"
response = requests.get(url)

GitHub’s user endpoint needs no key for basic data. The username goes straight into the path. (In production you’d URL-encode it, but GitHub usernames are already restricted to safe characters.)

github_profile_viewer.py
if response.status_code == 200:
    data = response.json()
    self.display_profile(data)
else:
    messagebox.showerror("Error", f"Failed to fetch profile. Status code: {response.status_code}")

404 means no such user, 403 usually means you hit the rate limit. Reporting the code turns a silent failure into a debuggable one.

github_profile_viewer.py
self.result_text.insert(END, f"Name: {data.get('name', 'N/A')}\n")
self.result_text.insert(END, f"Bio: {data.get('bio', 'N/A')}\n")

Many profiles leave name or bio empty, returning null. data.get('name', 'N/A') substitutes a default instead of printing None or raising KeyError. Use .get() for every optional field.

github_profile_viewer.py
self.result_text.delete(1.0, END)     # clear previous result
self.result_text.insert(END, ...)     # append lines

1.0 means “line 1, character 0”. Clearing before each fetch prevents profiles from stacking on top of each other.

Unauthenticated requests are capped at 60/hour; authenticated ones get 5,000/hour. Add a token:

auth.py
import os, requests
 
headers = {"Authorization": f"token {os.environ['GITHUB_TOKEN']}"}
r = requests.get("https://api.github.com/users/torvalds", headers=headers, timeout=10)

Create a token at GitHub → Settings → Developer settings → Personal access tokens. Store it in an environment variable, never in the code.

The JSON includes avatar_url:

avatar.py
from io import BytesIO
import requests
from PIL import Image, ImageTk     # pip install pillow
 
def load_avatar(url):
    raw = requests.get(url, timeout=10).content
    img = Image.open(BytesIO(raw)).resize((96, 96))
    return ImageTk.PhotoImage(img)

Display it in a Label(image=...) — remember to keep a reference or Tkinter garbage-collects the image.

A second endpoint returns repos; sort them by stars:

repos.py
def top_repos(username, n=5):
    r = requests.get(f"https://api.github.com/users/{username}/repos",
                     params={"sort": "updated", "per_page": 100}, timeout=10)
    repos = r.json()
    repos.sort(key=lambda x: x["stargazers_count"], reverse=True)
    return [(x["name"], x["stargazers_count"], x["language"]) for x in repos[:n]]

Fetch two profiles and lay them out side by side — followers, repos, and total stars. A great use of the API for “who’s more active” debates and a clean way to practice reusing your fetch function.

ProblemCauseFix
403 Forbidden after a few callsHit the 60/hour anonymous limitAuthenticate with a token (5,000/hour)
Prints None for name/bioField was nullUse data.get(field, "N/A")
Profiles stack up in the boxDidn’t clear the Text widgetdelete(1.0, END) before inserting
Avatar doesn’t showImage garbage-collectedKeep a reference: self.photo = ...
App hangs on slow networkNo timeoutAdd timeout=10 to every request
Crash on empty usernameNo input guardCheck if not username and return early
  1. Repo dashboard — table of repos with stars, forks, and language.
  2. Contribution stats — total stars across all repos, most-used language.
  3. Org viewer — switch the endpoint to /orgs/{name}.
  4. Follower explorer — list followers and click through to their profiles.
  5. Export — save a profile summary to JSON or a PDF card.
  6. Search — use /search/users to find profiles by query.
  7. Dark mode — theme the UI; show language colors like GitHub does.
  • Recruiting tools — quick candidate snapshots from a username.
  • Developer dashboards — track your own or your team’s activity.
  • OSS analytics — measure project reach and contributor influence.
  • Portfolio sites — auto-populate a “my GitHub” section.
  • REST APIs — endpoints, paths, JSON, status codes.
  • Rate limits & auth — tokens, headers, and being a good API citizen.
  • Defensive coding.get() defaults and status checks.
  • Working with images — fetching and rendering remote bytes in a GUI.
  • Add token authentication to lift the rate limit.
  • Render the avatar and a top-repos list.
  • Build the side-by-side comparison view.
  • Export profiles to JSON.

You built a GitHub profile viewer from one requests.get, then grew it into a dashboard with avatars, repo rankings, authenticated requests, and comparisons. The fetch → check status → safe-parse → display loop you practiced here is the exact shape of every API client you’ll write — only the endpoint changes. Full source on GitHub. Find more API projects on Python Central Hub.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading