Finished my GitHub CLI tool ########################### :date: 2021-11-05 00:08 :author: tyrel :category: Tech :tags: python, cli :slug: finished-my-github-cli-tool :status: published 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 `__. HTTPX.LINKS ----------- Today I learned some fun things with ``httpx`` mainly. The main thing I focused on today was figuring out pagination. The GitHub API uses the ``link`` 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: .. raw:: html
.. ``{'link': '; rel="next", ; rel="last"', ...}`` sample header value from Getting Andrey's repositories list and checking pagination .. raw:: html
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 ``httpx`` responses have this handled and ``client.get(...).links`` returns a lovely dictionary of the proper data. With a ``response.links.get('next')`` check, you can get the url from ``response.links['next']['url']``. So much nicer than writing some regular expressions. TESTING ------- With that accomplished, I then added ``pytest-cov`` to my ``requirements.in`` 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 ``pytest.raises`` and I was able to just ``with pytest.raises(httpx.HttpStatusError)`` 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 ``link`` header, checking the cases where there was NO ``link``, 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 ``rich.table.Table`` so I haven't been able to find a nice "this will make a string for you" without just using ``rich``'s ``print`` function. I decided the easiest check was to see if the ``Table.Columns.Cells`` 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 ``repo['column'], repo['column']`` 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 ``{k:v for k,v in repos if k in SELECTED_KEYS}`` 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 ``django-dbfilestorage``. 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 `__. The `Tidelift 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.