Finished my GitHub CLI tool
Tags: tech, python, cli
Published: 2023-02-01 02:38:06

---
title: Finished my GitHub CLI tool
published: true
date: 2021-11-05 04:08:00 UTC
tags: Tech,python,cli
canonical_url: https://tyrel.dev/blog/2021/11/finished-my-github-cli-tool.html
---

I never intended this to be a full fleshed CLI tool comparable to the likes of the real GitHub CLI. This was simply a way to refresh myself and have fun. I have accomplished this, and am now calling this _"Feature Complete"_. You can play around with it yourself from the [repository on gitlab](https://gitlab.com/Tyrel/ghub).

## HTTPX.LINKS

Today I learned some fun things with <tt>httpx</tt> mainly. The main thing I focused on today was figuring out pagination. The GitHub API uses the <tt>link</tt> header which I had never seen before.

The format of the header is a url, and then a relationship of that url. Lets take my friend Andrey's repos for example:

```
{'link': '<https://api.github.com/user/6292/repos?page=2>; rel="next", <https://api.github.com/user/6292/repos?page=7>; rel="last"', ...}
```

The link header there has two items split by a comma, each with two fields split by a semicolon, the first of which is a URL inside angle brackets... and AGHH this is going to be annoying to parse! Luckily <tt>httpx</tt> responses have this handled and <tt><span>client.get(...).links</span></tt> returns a lovely dictionary of the proper data.

With a <tt><span>response.links.get('next')</span></tt> check, you can get the url from <tt><span>response.links['next']['url']</span></tt>. So much nicer than writing some regular expressions.

## TESTING

With that accomplished, I then added <tt><span>pytest-cov</span></tt> to my <tt>requirements.in</tt> and was able to leverage some coverage checks. I was about 30% with the latest changes (much higher than anticipated!) so I knew what I wanted to focus on next. The API seemed the easiest to test first again, so I changed around how I loaded my fixtures and made it pass in a name and open that file instead. In real code I would not have the function in both my test files, I would refactor it, but again, this is just a refresher, I'm lazy.

I decided earlier that I also wanted to catch HTTP 403 errors as I ran into a rate limit issue. Which, _I assure you dear reader_, was a thousand percent intentional so I would know what happens. Yeah, we'll go with that.

Py.Test has a context manager called <tt>pytest.raises</tt> and I was able to just <tt>with pytest.raises(httpx.HttpStatusError)</tt> and check that raise really easily.

The next bits of testing for the API were around the pagination, I faked two responses and needed to update my <tt>link</tt> header, checking the cases where there was NO <tt>link</tt>, was multiple pages, and with my shortcut return - in case the response was an object not a list. Pretty straight forward.

The GHub file tests were kind of annoying, I'm leveraging <tt>rich.table.Table</tt> so I haven't been able to find a nice "this will make a string for you" without just using <tt>rich</tt>'s <tt>print</tt> function. I decided the easiest check was to see if the <tt>Table.Columns.Cells</tt> matched what I wanted, which felt a little off but it's fine.

The way I generated the table is by making a generator in a pretty ugly way and having a bunch of <tt><span>repo['column'],</span> <span>repo['column']</span></tt> responses, rather than doing a dict comprehension and narrowing the keys down. If I ever come back to this, I MIGHT reassess that with a <tt>{k:v for k,v in repos if k in SELECTED_KEYS}</tt> and then yield a dictionary, but it's not worth the effort.

Overall I'd say this project was fun. It gave me a glimpse back into the Python world, and an excuse to write a couple blog posts. My next project is to get a Django site up and running again, so I can figure out how to debug my <tt><span>django-dbfilestorage</span></tt>.

## Closing Thoughts

If I had to do this again, I would probably have tried some test driven development. I've tried in the past, but I don't work on a lot of greenfield projects. I tend to be the kind of engineer who jumps HEAD FIRST into code and then tests are an after thought.

I also kind of want to rewrite this in Go and Rust, two other languages I've been fond of lately, just to see how they'd compare in fun. I haven't done any API calls with Rust yet, only made a little Roguelike by following [Herbert Wolverson's Hands-On-Rust book](https://pragprog.com/titles/hwrust/hands-on-rust/). The [Tidelift CLI](https://tidelift.com/cli) is all Go and a bazillion API calls (okay like ten) so that wouldn't be too hard to use like SPF13's Cobra CLI library and make a quick tool that way.

One fun thing I learned while moving things over to GitLab is that my user Tyrel is a super early adopter. I was in the first 36,000 people! I showed a screenshot of my user ID to my friend Sunanda at GitLab and we had fun finding that out.