Adventures in Python Web Services
Key Differences With Python
Syntax
Before we rush into webservices, let's have a quick primer with python itself. This will throw most people off right away, it doesn't have braces. Take the following statement
Program Control
if(myBoolean) {
doSomething()
}
That won't compile in python, instead it'll look like this
if myBoolean:
doSomething()
Be warned, the indentation is important - if the indentation is incorrect or even if the number of spaces changes it won't work.
You might have noticed a few other things:
- Semicolons aren't necessary, or used.
- Variables aren't declared with a type or 'var' (although python 3 has typing available, more on that later).
- If statements predicates do not need to be wrapped in ().
You'll need to know a few other basics to begin so let's quickly enumerate some of those:
Defining a function:
Let's print a variable before we return the incremented value. If the value is None of course None is the result.
def my_function(num):
if num is not None:
print(num)
return num + 1
else:
return None
So a function starts with def
and the indentation is as per an if statement, also notice a return type isn't needed (again python 3 adds a standard type hinting that's optional, more on this later).
You can also see that python doesn't use null
it uses None
(Capital) and you don't test for equality (==
) you test for membership (is
) as per the example.
Of course, Python gives you other syntax options, it doesn't have the 'ternary conditional' operator, but you can roll up A if CONDITION else B, where is somewhat more elegant. Also don't forget, unlike other languages that needed to add nullables, all python types are capable of being None, because it's not strongly typed.
def my_function(num):
if num is not None: print(num)
return num +1 if num is not None else None
Using it in a Web Service
Ok, you wanna get to the good stuff? Let's do it. We will use the Bottle framework bottlepy.org, it's capable of running it's own test server for our purposes, but usually it's preferable to run it under UWSGI (with something like NGINX to act as a frontend).
Firstly, If you don't have bottle please install it
Using pip install bottle
or pip3 install bottle
depending on if you want Python2 (linux default) or Python3 (more preferable these days).
Here you will see python's import statement.
Not only can you import a whole module (think other python file if it helps) by import module
, but you can choose classes from that module to import (Yes, python has classes, but that's a lesson for later) - the syntax is reversed from module import item
.
from bottle import route, run, response, default_app, request, get
@route('/api/odd_or_even', method='GET')
def determine_oddness():]
number = int(request.params["number"])
description = "Even" if (number/2)*2 == number else "Odd"
return template('The number {{number}} is {{description}}', name=name, description= description )
# Create app instance
app = application = default_app()
# Run bottle internal test server when invoked directly ie: non-uxsgi mode
if __name__ == '__main__':
run(app=app, host='0.0.0.0', port=8080)
Whew! That was a lot.
Let's break it down:
The import statement should be fairly obvious as those things are all referenced, let's look at what's probably got your attention, the @route
directive:
@route('/api/odd_or_even', method='GET')
This tells the framework that the method is accessible via myserver.com/api/odd_or_even, and that the HTTP method type is 'GET' (meaning it has no body and will only support query string parameters). Fairly neat huh!
Next we convert the input from a string (we've assumed no error handling here, let's revise that later)
number = int(request.params["number"])
So for example if you request http://myserver.com/api/odd_or_even?number=1
then your variable number is 1, most type conversions (string, int) are called as methods with that type's names, c-style casts of (int)
etc are not support or don't even make sense as python is not so strongly typed.
After that we see the ternary style equivalent in Pythohn to give one of two values depending on the conditional "Even" or "Odd" result, that one doesn't need repeating. Since the value is an int we (ab)use the fact that precision is lost during dividing so (number/2)*2 == number
means that if the number is 3, 3/2=1,*2 = 2
, hence it's odd.
Now here's some templating, it's Bottle's super basic (I recommend perhaps using Jinja for anything more advanced, but Bottle's is built in and handles stuff fairly well as a minimum):
return template('The number {{number}} is {{description}}', name=name, description= description )
Regardless if you use Bottle or Jinja for templating {{SOMEVAR}}
is the accepted norm for substituion like <% var %> in asp or <?php ?> in php. There is some more advanced stuff available (especially in Jinja) like looping, but for now this should be enough to see that it substitutes it.
Of course Python also supports string formats (and is actually simpler), so let's examine that, although the point here was to show templating. This will come in useful in any case, as you can see arguments are provided in order, however alternative ways of providing them (think name) may be used too.
return "The number {0} is {1}".format(number, description)
After that it's just a matter of setting up the app server (demo in this case). default_app()
gets the default app which is good enough for us.
Finally, we check (or make sure in this case) we are running as a dev or test server (i.e. by invoking pyhton my_App.py
):
if __name__ == '__main__':
run(app=app, host='0.0.0.0', port=8080)
That should be fairly self explanatory, __name__ is a special variable to give us the method name, in this case again __main__ which is what other langauges would refer to as void main(){}
, however with Python the script just starts running at any lines that aren't method or class definitions etc.
So give this a run, then sit back and congratulate yourself, you've just made your first API with Python's Bottle framework. Deploying it to production you'll find isn't too difficult, but that's an exercise for another day.