Building Web APIs: Give Python A Little “Hug”



I really like Python as a systems language; I HATE Python as a web language. It’s probably because I’m old, hard-headed and set in my ways, but every Python “web thing” seems to complicate things that have been simple for over a decade, but makes complicated things simple, at least to me. The problem is, I’m a really, really simple guy. I’ve been coding (poorly) for over twenty years -- over 30 if you go back to my teenage years, which were filled with shenanigans. So I’ve seen languages come and go and come back again. I say all that to say this, Python for web junk just got a whole lot better for me. Stick around and I'll tell you why, or turn away now. The choice is yours.


We recently had (and still have) a need to convert a lot of command line tools that we use internally into web services for use in our new, freshly upgraded portal (that clients will likely see between now and 2025). We need to be able to simply take something, whether that's something we’ve been using for years or something new, and "webify" it.

There are all sorts of hacky ways to do this in just about every language. But the one I turned to, so that I could really hack it up, was Python. Lots of reasons for this, but primarily because most of our internal stuff is written in Python, so it was a good choice all around and I figured “hey, we just need to spit out some results from existing things, so no need to reinvent the wheel.” In simpler terms, we didn’t have to reproduce all of the logic from the tool, just the input and the results. This is when I was given a little hug.


Now, if you’re still with me, let’s look at why hug is so neat. Fair warning, their tutorial is WAY better than mine, so definitely check out their website.

Alright, let’s write some simple code that really highlights hug’s features. Let’s say I want to write a web service (scary) that takes some input and queries the CVE repository over at Mitre. Here we go:

import hug
Import requests

@hug.get(‘/cve’, versions=1) 
#this tells hug to build a “route” to the function cve and make it available for
versions 1 and 2 (I will explain this later) 

def cve(cve_number):
	get_cve = requests.get(''+cve_number)
   	return get_cve

Pretty simple, huh? Told you I was a simple guy. Let’s run this and see what we get.

Running any Python web thing in production is the topic of several blogs posts and many more books. It is unnecessarily complicated at times but basically you need something that understands and interprets the code, and something to really serve it, usually a reverse proxy. All the cool kids are using Nginx these days, which is why I stick with Apache, but you can pick your poison there. Regardless, for our purposes, we’ll use Hug’s built web stuff. Do NOT ever use this in production, you have been warned.


On the command line run hug -f (assuming that’s what you named it):

Well, it not only generates amazing ASCII art, but also tells us it is serving our content, in this case, on port 8000. The second line there? Yeah, that’s someone who tried to connect to my web service about two or three seconds after I started it (it’s on a nice open server because I’m a #security guy!). He’s nice, so I blurred out his IP.

I'm also running this service as root, which is something you should never do either... I didn’t say I was a good security guy and also this fortunately isn’t anywhere near production box. Remember, I’m also a developer and sometimes that trumps security guy.

ANYWAY! Now that we have our little web service running, let’s look at the output.

As you can see I’m connected to it on port 8000 and I pass in the cve_number to the /cve route like you would any parameter. Hug will then lovingly execute my CVE with cve_number being equal to the CVE number in my URL, in this case CVE-2016-1347.

Mitre’s website isn’t really designed to be used this way, so it just spits out a bunch of XML. In a perfect world (way beyond the scope of this article) we would convert that to JSON and then we would be able to use it in some other code. I only used CVE in this instance because this blog had to have something to do with security.


The nicest thing about hug, especially for people who don’t like to really document stuff, is that it is virtually self-documenting. The screenshot shows you what happens if you just call the web service and don’t quite get the URL right.

This tells us everything we need to know about both the API call and what we should expect in return. If you hate documenting things correctly (or at all) then you'll love this and immediately understand how neat it is.

Alright, we’ve done a lot with hug with very little code and this is what makes it really great to work with. The final thing I’m going to cover here, mainly because it is near and dear to my heart, is versioning.


Versioning an API should be simple, but it almost never is. Until I found hug that is. Did you notice in our route statement we had "versions=1"? That is awesome because later we can write another identical route and then say "versions=2", which will allow us to have a completely different version of the API, all the while allowing the first version to work.

I’ll give you an anecdotal example. I used hug to build an API that returned some JSON for use in Splunk and a few other things we use. Now, without getting too technical, Splunk hates nested JSON, it just does. It can deal with it, but no one likes making it do that. So for use in Splunk you need to “flatten” the JSON (at least we do). I had a route that returned beautiful nested JSON. So I simply wrote another function and route (with the same same) and said “versions=2” in the declaration and boom, I had a "version 2" of the API for Splunk to use which flattened the JSON but still had "version 1" for everything else. This is a pretty powerful thing when you think about it.


That was just a little tour of the power hug has to build web APIs, so I really encourage you to check it out. Of course, there are a lot of things I left out of this mini-tutorial -- like you should probably make sure you can only pass in a CVE number as your parameter and the best part is, you can do this in pure Python without having to do a single thing in hug.

Hug is designed to get out of your way and focus on your code, not silly web stuff you probably don’t really care about anyway. Give it a spin and come back to yell at me about how I did something wrong later. Enjoy.