Skip to content

Instantly share code, notes, and snippets.

@mfonism
Last active October 31, 2020 07:53
Show Gist options
  • Save mfonism/42b62420c6eb6be2abe80eb1fe5f103f to your computer and use it in GitHub Desktop.
Save mfonism/42b62420c6eb6be2abe80eb1fe5f103f to your computer and use it in GitHub Desktop.
Notes on my experience learning aiohttp (and asyncio) by building a RESTful API (https://github.com/mfonism/Qriosity)

Battling with the Test Client

The aiohttp_client fixtures from pytest_aiohttp was (and still is) an excellent idea. The promise I saw in it was that I would be able to run my tests the way I do while working with Django. That is, the test runner will automatically run the app for the duration of the tests, a db will be created specifically for the tests, and the tests will be run in isolation with respect to each other.

However, this did not work out so well for me. aiohttp_client kept erroring out with a warning that the event loop was already in use.

In attempting to solve this problem, I learnt how to create a session-scoped event loop for tests - the default loop exposed by pytest_asyncio is function scoped, and a number of the fixtures I had created at that iteration of the app were session scoped and required a session scoped event loop.

A Honking Great Idea

I asked on StackOverflow how I could find my way around the problem with aiohttp_client. The question got quite a number of views, but no answers. Not even comments.

And time, being what it is, wasn't on my side.

So I had to sit for ideas on how to work around that.

I first went with the crude idea of running the app every time I had to run the tests. This was crude because once run there was no way to restart the app in response to changes I made to the source code.

If a test failed, I'd have to shut down the app, write code to fix the failure, then run the app again. Which seems totally doable in theory. Until you realize that if you're just like me, and you don't always get your corrections right the first time, there'd be lots of hair pulling moments.

After doing that dance a hundred times just to include a correction which seemed rather trivial, I set out to find a way to hot reload my app.

The Short, Very Short Affair with Gunicorn

My research landed me at gunicorn and a number of other tools/methods.

Now, I was looking to roll out a solution as close as possible to scratch. Because, well, learning. And then also, I wanted to avoid bloating up my dependencies.

But I recognized I was eventually going to make use of gunicorn to serve the app with nginx in production. So I decided to just use it already.

I really got into the groove of hot reloading with gunicorn.

And when I ran my code, the harsh reality of developing on Windows slapped me for the thousandth time in my short developer life.

You see, my code depended on a module in gunicorn which in turn depended on the Python library grp, which was not available on the Windows installation of Python.

I really considered quitting aiohttp.

For life.

Like I had done with Flask in 2014.

I mean, I had already started giving myself a lecture on the ills of playing with minimalist frameworks/libraries, and why one should stick to batteries-included frameworks, when the non-quitter in me reminded me that it was no longer 2014.

That it was 2020, that six years had gone past, and that I was no longer that boy who had showed the middle finger to computer programming and had gone on an eternal break because he'd spent a week working hard at a problem whose solution turned out to be something along the lines of "real developers use Linux".

That I had grown a lot since then.

Which worked for me, because I went to bed with a mental note to tackle the problem with a fresh mind the next day.

Hello Watchgod!

Voice of French Narrator from SpongeBob: Four Hours Later...

There's this thing that happens to me where I go to bed with an unsolved problem, and won't be able to sleep until I've fagged myself out thinking up solutions (which more of than not elude me).

It happened this time, too.

It was a hot night and there was no power supply. I spent the first hour and a half rolling on the bed...

By morning, I didn't have a solution in mind. But I knew where I was going to start.

I was going to start with a utility to detect changes in my source directory, then another for restarting the app. I'd just have to tie the two together so that the app gets restarted whenever a change is made to the source directory.

My research led me to watchgod, and I shamelessly borrowed from it.

Leveraging on this amazing library, I wrote a hotreloader which runs a target function in a subprocess using multiprocessing.Process. The hotreloader listens for changes to files on a specified path, and responds to detected changes by stopping and then starting the aforementioned subprocess.

With that in place, running tests was no longer a hair-pulling exercise for me.

I'd just keep my test server running while developing the site. Any changes to the source folder will be detected, and the server will be hotreloaded.


Improvements?

It's all good, the way I'm currently doing things, but I know it could be better.

I mean, when working with Django, I don't have to keep my server running.

Running the tests will cause the test server to run. And at the end of the tests, the server will be stopped.

I look forward to such a setup in this project, but till then, I'm happy with what I have.

So, I'm going to take that deserved two-hour break. I'll join my housemates in their soiree on the rooftop. We'll watch an episode of RuPaul's Drag race, we'll lip sync for our lives, we'll play charades, we'll take fiery selfies, and when my two hours are up I'll sashay away.

XOXO, @mfonism

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment