I have built a generator that, using the GitHub API, creates a dictionary containing a tree of all the resources in any GitHub repo. It uses the function git_tree
, which takes one argument, a repo-string (repo-author/repo-name
). For example, this repo only contains one file, a README
. If we pass in the repo-string, and format the end result, this is what we get:
>> json.dumps(git_tree("githubtraining/github-move"), indent=3)
{
"github-move": {
"files": [
"README.md"
],
"dirs": {}
}
}
You can see that it returns a dict
with a key/value pair of {repo_name: tree}
. So the github_move
item contains a list of all files in that directory, and a dict with more nested directories. Obviously, in this repository there aren't any other directories, so that dict is just blank.
For sake of purpose, here is the tree of this repo (it was too long to put in the post). You can see each directory and subdirectory has its own files
list and dirs
dict.
Here's the code (repl.it online program for testing):
import requests
from pprint import pprint
from functools import reduce
import operator
import json
from itertools import chain, repeat, islice
class GitError(Exception): pass
def intersperse(delimiter, seq):
return list(islice(chain.from_iterable(zip(repeat(delimiter), seq)), 1, None))
def _get_from_dict(dataDict, mapList):
return reduce(operator.getitem, mapList, dataDict)
def _append_in_dict(dataDict, mapList, value):
_get_from_dict(dataDict, mapList[:-1]).append(value)
def _get_sha(author, repo):
try:
return requests.get('https://api.github.com/repos/{}/{}/branches/master'.format(author, repo)).json()['commit']['commit']['tree']['sha']
except KeyError as ex:
raise GitError("Invalid author or repo name") from ex
def _get_git_tree(author, repo):
return requests.get("https://api.github.com/repos/{}/{}/git/trees/{}?recursive=1".format(author, repo, _get_sha(author, repo))).json()["tree"]
def git_tree(repostring):
author, repo = repostring.split("/")
tree = {repo: {"files": [], "dirs": {}}}
for token in _get_git_tree(author, repo):
if token["type"] == "tree" and "/" not in token["path"]:
tree[repo]["dirs"].update({token["path"]: {}})
tree[repo]["dirs"][token["path"]].update({"files": [], "dirs": {}})
elif token["type"] == "tree" and "/" in token["path"]:
temp_dict = {}
a = list(reversed(token["path"].split("/")))
for k in a[:-1]:
temp_dict = {k: {"files": [], "dirs": temp_dict}}
tree[repo]["dirs"][a[-1]]["dirs"] = temp_dict
elif token["type"] == "blob":
path = token["path"].split("/")
if len(path) == 1:
tree[repo]["files"].append(path[0])
else:
dict_path = [repo, "dirs"] + intersperse("dirs", path[:-1]) + ["files", path[-1]]
_append_in_dict(tree, dict_path, dict_path[-1])
return tree
print(json.dumps(git_tree("githubtraining/caption-this"), indent=3))
(The json.dumps
is just there for easy viewing, it can be ommited).
My questions:
Is it too messy? I look back at this function and it looks a bit cluttered and all over the place. Is that the case, or am I just going crazy?
Do I have any unnecessary code in there?
Is there anything else you deem wrong with the program?