Python Central HubSign in

Python Projects / Intermediate

GitHub Profile Viewer

Build a Tkinter app in Python that fetches and displays any GitHub user's profile via the GitHub REST API — then add avatars, repo lists, rate-limit handling, authentication, and a side-by-side profile comparison.

Abstract

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.

Prerequisites

  • 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.

Getting Started

Create the project

  1. Create a folder named github-profile-viewer.
  2. Inside it, create github_profile_viewer.py.
  3. Install the dependency: pip install requests.

Write the code

github_profile_viewer.pySource
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()

Run it

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

How it fits together

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

Step-by-Step Explanation

1. The endpoint

Python
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.)

2. Check the status code

Python
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.

3. Safe field access

Python
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.

4. The Text widget

Python
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.

Beat the Rate Limit with Authentication

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

Python
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.

Show the Avatar

The JSON includes avatar_url:

Python
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.

List the User's Repositories

A second endpoint returns repos; sort them by stars:

Python
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]]

Compare Two Developers

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.

Common Mistakes

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

Variations to Try

  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.

Real-World Applications

  • 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.

Educational Value

  • 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.

Next Steps

  • 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.

Checkpoint exercise

This checkpoint practices summarising a GitHub API response: parse the JSON, total the stars and count languages, using an inline sample instead of the network.

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Conclusion

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.

Was this page helpful?

Let us know how we did