Session 10

Published

October 30, 2023

Topics Covered
  • Classes and Objects

Problem: List Github Repos

%load_problem github-repos
Problem: List Github Repos

Write a program github_repos.py to list the github repos of a user given the access token.

The program should accept the access token using command-line flag -t and print full name of all the repositories of that user. A suffix of * should be added if the repository is private.

$ python github_repos.py -t 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
PipalBot/hello-world
PipalBot/python-assignment-01*
PipalBot/python-assignment-02*

Assume that the command-line flag -t is always provided.

The Github API

The List repositories for the authenticated user section in the Github developer docs has required information about how to use this API.

You can verify your solution using:

%verify_problem github-repos

%%file github_repos.py
import argparse
import requests

p = argparse.ArgumentParser()
p.add_argument("-t", "--token")
args = p.parse_args()

headers = {
    "Authorization": f"Bearer {args.token}"
}
url = "https://api.github.com/user/repos"
repos = requests.get(url, headers=headers).json()
for repo in repos:
    private = "*" if repo['private'] else ""
    print(repo['full_name'] + private)
Overwriting github_repos.py
# !curl \
#     -H 'Authorization: Bearer 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' \
#     https://api.github.com/user/repos
!python github_repos.py -t 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
PipalBot/hello-world
PipalBot/python-assignment-01*
PipalBot/python-assignment-02*
PipalBot/test-01
PipalBot/test-02*
%verify_problem github-repos
✓ python github_repos.py -t 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 | grep -v test
🎉 Congratulations! You have successfully solved problem github-repos!!

Example: Github Library

class Github:
    """Interface to the Github API.
    """
    def __init__(self, token):
        self.token = token

    def request(self, method, path):
        url = "https://api.github.com" + path
        headers = {
            "Authorization": f"Bearer {self.token}"
        }        
        response = requests.request(method=method, url=url, headers=headers)
        return response

    def get(self, path):
        return self.request("GET", path)

    def me(self):
        data = self.get("/user").json()
        return GithubUser(data)

    def get_repos(self):
        """Returns all the repositories of the active user.
        """
        data = self.get("/user/repos").json()
        return [GithubRepository(self, d) for d in data]

class GithubUser:
    def __init__(self, data):
        self.data = data
        self.login = data['login']

    def __repr__(self):
        return f"<GithubUser: {self.login}>"

class GithubRepository:
    def __init__(self, github, data):
        self.github = github
        
        self.data = data
        self.name = data['name']
        self.full_name = data['full_name']
        self.private = data['private']
        self.owner = GithubUser(data['owner'])

    def get_contributors(self):
        """Returns all the contributors to this repository.
        """
        path = f"/repos/{self.full_name}/contributors"
        data = self.github.get(path).json()
        return [GithubUser(d) for d in data]

    def __repr__(self):
        private = "*" if self.private else ""
        return f"<Repo: {self.full_name}{private}>"
token = open("/etc/pipalbot/access-token.txt").read().strip()
gh = Github(token=token)
# gh.get("/user").json()
gh.me()
<GithubUser: PipalBot>
gh.get_repos()
[<Repo: PipalBot/hello-world>,
 <Repo: PipalBot/python-assignment-01*>,
 <Repo: PipalBot/python-assignment-02*>,
 <Repo: PipalBot/test-01>,
 <Repo: PipalBot/test-02*>]
repos = gh.get_repos()
repos[0].full_name
'PipalBot/hello-world'
# [repo.full_name for repo in repos if not repo.private]
# repos[0].data['owner']
repos[0].owner
<GithubUser: PipalBot>
# gh.get("/repos/PipalBot/hello-world/contributors").json()
repo = repos[0]
repo
<Repo: PipalBot/hello-world>
repo.get_contributors()
[{'login': 'anandology', 'id': 7569, 'node_id': 'MDQ6VXNlcjc1Njk=', 'avatar_url': 'https://avatars.githubusercontent.com/u/7569?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/anandology', 'html_url': 'https://github.com/anandology', 'followers_url': 'https://api.github.com/users/anandology/followers', 'following_url': 'https://api.github.com/users/anandology/following{/other_user}', 'gists_url': 'https://api.github.com/users/anandology/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/anandology/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/anandology/subscriptions', 'organizations_url': 'https://api.github.com/users/anandology/orgs', 'repos_url': 'https://api.github.com/users/anandology/repos', 'events_url': 'https://api.github.com/users/anandology/events{/privacy}', 'received_events_url': 'https://api.github.com/users/anandology/received_events', 'type': 'User', 'site_admin': False, 'contributions': 1}, {'login': 'PipalBot', 'id': 126411761, 'node_id': 'U_kgDOB4jj8Q', 'avatar_url': 'https://avatars.githubusercontent.com/u/126411761?v=4', 'gravatar_id': '', 'url': 'https://api.github.com/users/PipalBot', 'html_url': 'https://github.com/PipalBot', 'followers_url': 'https://api.github.com/users/PipalBot/followers', 'following_url': 'https://api.github.com/users/PipalBot/following{/other_user}', 'gists_url': 'https://api.github.com/users/PipalBot/gists{/gist_id}', 'starred_url': 'https://api.github.com/users/PipalBot/starred{/owner}{/repo}', 'subscriptions_url': 'https://api.github.com/users/PipalBot/subscriptions', 'organizations_url': 'https://api.github.com/users/PipalBot/orgs', 'repos_url': 'https://api.github.com/users/PipalBot/repos', 'events_url': 'https://api.github.com/users/PipalBot/events{/privacy}', 'received_events_url': 'https://api.github.com/users/PipalBot/received_events', 'type': 'User', 'site_admin': False, 'contributions': 1}]
[<GithubUser: anandology>, <GithubUser: PipalBot>]

Issues

# gh.get("/repos/PipalBot/hello-world/issues").json()
class Github:
    """Interface to the Github API.
    """
    def __init__(self, token):
        self.token = token

    def request(self, method, path):
        url = "https://api.github.com" + path
        headers = {
            "Authorization": f"Bearer {self.token}"
        }        
        response = requests.request(method=method, url=url, headers=headers)
        return response

    def get(self, path):
        return self.request("GET", path)

    def me(self):
        data = self.get("/user").json()
        return GithubUser(data)

    def get_repos(self):
        """Returns all the repositories of the active user.
        """
        data = self.get("/user/repos").json()
        return [GithubRepository(self, d) for d in data]

    def get_issues(self):
        """Returns all the issues assigned to the active user.
        """
        pass

class GithubUser:
    def __init__(self, data):
        self.data = data
        self.login = data['login']

    def __repr__(self):
        return f"<GithubUser: {self.login}>"

class GithubRepository:
    def __init__(self, github, data):
        self.github = github
        
        self.data = data
        self.name = data['name']
        self.full_name = data['full_name']
        self.private = data['private']
        self.owner = GithubUser(data['owner'])

    def get_contributors(self):
        """Returns all the contributors to this repository.
        """
        path = f"/repos/{self.full_name}/contributors"
        data = self.github.get(path).json()
        return [GithubUser(d) for d in data]

    def get_issues(self):
        """Returns all the issues in this repository.
        """
        path = f"/repos/{self.full_name}/issues"
        data = self.github.get(path).json()
        return [GithubIssue(self, d) for d in data]
        
    def __repr__(self):
        private = "*" if self.private else ""
        return f"<Repo: {self.full_name}{private}>"

class GithubIssue:
    def __init__(self, repo, data):
        self.repo = repo        
        self.data = data

        self.number = data['number']

    def __repr__(self):
        return f"<Issue: {self.repo.full_name}#{self.number}>"
gh = Github(token)
repos = gh.get_repos()
repo = repos[0]
issues = repo.get_issues()
issues
[<Issue: PipalBot/hello-world#2>, <Issue: PipalBot/hello-world#1>]
issues[0].data
{'url': 'https://api.github.com/repos/PipalBot/hello-world/issues/2',
 'repository_url': 'https://api.github.com/repos/PipalBot/hello-world',
 'labels_url': 'https://api.github.com/repos/PipalBot/hello-world/issues/2/labels{/name}',
 'comments_url': 'https://api.github.com/repos/PipalBot/hello-world/issues/2/comments',
 'events_url': 'https://api.github.com/repos/PipalBot/hello-world/issues/2/events',
 'html_url': 'https://github.com/PipalBot/hello-world/issues/2',
 'id': 1967527612,
 'node_id': 'I_kwDOKmtiFM51Rha8',
 'number': 2,
 'title': 'An issue assigned to another user',
 'user': {'login': 'PipalBot',
  'id': 126411761,
  'node_id': 'U_kgDOB4jj8Q',
  'avatar_url': 'https://avatars.githubusercontent.com/u/126411761?v=4',
  'gravatar_id': '',
  'url': 'https://api.github.com/users/PipalBot',
  'html_url': 'https://github.com/PipalBot',
  'followers_url': 'https://api.github.com/users/PipalBot/followers',
  'following_url': 'https://api.github.com/users/PipalBot/following{/other_user}',
  'gists_url': 'https://api.github.com/users/PipalBot/gists{/gist_id}',
  'starred_url': 'https://api.github.com/users/PipalBot/starred{/owner}{/repo}',
  'subscriptions_url': 'https://api.github.com/users/PipalBot/subscriptions',
  'organizations_url': 'https://api.github.com/users/PipalBot/orgs',
  'repos_url': 'https://api.github.com/users/PipalBot/repos',
  'events_url': 'https://api.github.com/users/PipalBot/events{/privacy}',
  'received_events_url': 'https://api.github.com/users/PipalBot/received_events',
  'type': 'User',
  'site_admin': False},
 'labels': [],
 'state': 'open',
 'locked': False,
 'assignee': None,
 'assignees': [],
 'milestone': None,
 'comments': 0,
 'created_at': '2023-10-30T05:14:00Z',
 'updated_at': '2023-10-30T05:14:00Z',
 'closed_at': None,
 'author_association': 'OWNER',
 'active_lock_reason': None,
 'body': None,
 'reactions': {'url': 'https://api.github.com/repos/PipalBot/hello-world/issues/2/reactions',
  'total_count': 0,
  '+1': 0,
  '-1': 0,
  'laugh': 0,
  'hooray': 0,
  'confused': 0,
  'heart': 0,
  'rocket': 0,
  'eyes': 0},
 'timeline_url': 'https://api.github.com/repos/PipalBot/hello-world/issues/2/timeline',
 'performed_via_github_app': None,
 'state_reason': None}

Functions with variable and keyword arguments


def diff(x, y):
    return x-y
diff(6, 2)
4
diff(x=6, y=2)
4
diff(y=2, x=6)
4

If we have all the argument as a dictionary, how can we call the function?

kwargs = {"x": 6, "y": 2}
def square(x):
    return x
square(4)
4
print(1)
1
print(1, 2)
1 2
print(1, 2, 3)
1 2 3

The print function can take as many arguments that we give.

Similarly, dict takes as many keyword arguments that we give.

dict(x=1, y=2)
{'x': 1, 'y': 2}
def foo(*args):
    print(args)
foo(1)
(1,)
foo(1, 2, 3)
(1, 2, 3)
def foo(*args, **kwargs):
    print(args)
    print(kwargs)
foo()
()
{}
foo(1, 2, 3)
(1, 2, 3)
{}
foo(1, 2, 3, x=1,y=2)
(1, 2, 3)
{'x': 1, 'y': 2}
def strjoin(sep, *words):
    return sep.join(words)
strjoin("-", "a", "b", "c")
'a-b-c'
# strjoin with optional seperator
def strjoin(*words, sep=" "):
    return sep.join(words)
strjoin("a", "b", "c")
'a b c'
strjoin("a", "b", "c", sep=":")
'a:b:c'
args = ["a", "b", "c"]
strjoin(*args)
'a b c'

Back to github API

# github API v3
class Github:
    """Interface to the Github API.
    """
    def __init__(self, token):
        self.token = token

    def request(self, method, path, **kwargs):
        url = "https://api.github.com" + path
        headers = {
            "Authorization": f"Bearer {self.token}"
        }        
        response = requests.request(method=method, url=url, headers=headers, **kwargs)
        return response

    def get(self, path, **kwargs):
        return self.request("GET", path, **kwargs)

    def post(self, path, **kwargs):
        return self.request("POST", path, **kwargs)

    def me(self):
        data = self.get("/user").json()
        return GithubUser(data)

    def get_repos(self):
        """Returns all the repositories of the active user.
        """
        data = self.get("/user/repos").json()
        return [GithubRepository(self, d) for d in data]

    def get_issues(self):
        """Returns all the issues assigned to the active user.
        """
        pass

class GithubUser:
    def __init__(self, data):
        self.data = data
        self.login = data['login']

    def __repr__(self):
        return f"<GithubUser: {self.login}>"

class GithubRepository:
    def __init__(self, github, data):
        self.github = github
        
        self.data = data
        self.name = data['name']
        self.full_name = data['full_name']
        self.private = data['private']
        self.owner = GithubUser(data['owner'])

    def get_contributors(self):
        """Returns all the contributors to this repository.
        """
        path = f"/repos/{self.full_name}/contributors"
        data = self.github.get(path).json()
        return [GithubUser(d) for d in data]

    def get_issues(self):
        """Returns all the issues in this repository.
        """
        path = f"/repos/{self.full_name}/issues"
        data = self.github.get(path).json()
        return [GithubIssue(self, d) for d in data]

    def create_issue(self, title, body):
        """Creates a new issue.
        """
        path = f"/repos/{self.full_name}/issues"
        payload = {
            "title": title,
            "body": body
        }
            
        data = self.github.post(path, json=payload).json()
        return GithubIssue(self, data)
        
    def __repr__(self):
        private = "*" if self.private else ""
        return f"<Repo: {self.full_name}{private}>"

class GithubIssue:
    def __init__(self, repo, data):
        self.repo = repo        
        self.data = data

        self.number = data['number']
        self.title = data['title']
        self.body = data['body']

    def __repr__(self):
        return f"<Issue: {self.repo.full_name}#{self.number}>"
from tabulate import tabulate

gh = Github(token)
repos = gh.get_repos()
repo = repos[0]

issues = repo.get_issues()

data = [[issue.number, issue.title] for issue in issues]
print(tabulate(data, headers=["#", "Title"]))
  #  Title
---  ---------------------------------
  3  The third issue
  2  An issue assigned to another user
  1  Create a github issue
repo.create_issue("The fourth issue", "description to be filled in")
<Issue: PipalBot/hello-world#4>