Python School API

So let's build an API for Schools in Python. The first thing we need to do is make sure we can install this.

Install HomeBrew

First, if you´re like me and using a Mac, you probably know that High Sierra comes with Python 2.7 out of the box. However, Python.org does not recommend that you build applications on 2.7, and it will be EOL (End Of Life) 2020. So let's fix this and install 3.X instead.

You can do this in two ways. Both are easy. One is to download the package here and install like any application on mac. When writing this is 3.7.0.

Alternatively, what I prefer is Homebrew, a good package manager! Also, it makes it easy to install new packages and keeping them updated. Open a Terminal window.

Check if you already got it by just typing:

brew -v

If you see a version, you´re good to go. Otherwise, you can easily install it by typing

 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Find more info here.

install Python 3

When Homebrew is installed, we can install Python 3 with this command:

brew install python

When finished, run:

python3 -V

So your new Python version is installed!

PS. If you use VSC, you can choose the version of python, and it will use the right version right away.

Setup MongoDB for our data source

We are also going to use MongoDB for the data source. Let's install it with brew:

brew install mongodb

You also want to start it, and at least for me I want MongoDB to start every time I log in, so I use this to trigger autostart on login with launchd:

brew services start mongodb

however, if you want to start it when needed you can fire this instead:

mongod --config /usr/local/etc/mongod.conf

The current version is 4.0.0

Setup virtualenv for our project

pip3 install virtualenv
virtualenv --version

It is 16.0.0 while writing this.

virtualenv -p python3 SchoolAPI

install dependencies for our virtualenv with pip

So back to console:

pip3 install flask flask-restful bson pymongo

Setup Libs with pip

Let's continue with setting up some packages needed to build an API with Python. I am going to use Flask since it is easy and then use the requests-lib also.

So what do we need to make this happen?

flask & flask-restful

pymongo and its data model bson

virtualenv

So now we go all we need to begin writing some code!

Now go to the folder where we have our projects. I am using VSC and using the terminal integrated into VSC. Type this to create an environment and all we need to create a working project:

virtualenv -p python3 SchoolAPI

then go into the folder with

cd SchoolAPI

To be able to call Flask applications we need to activate the virtual environment, this is relatively simple, there is already a bash-script for this, so let's fire it by typing following in the root directory:

source bin/activate

Now we are ready to start developing our API! :D

So let's start and create a main.py file and save it.

So let's write some code and then break it down:

from flask import Flask, jsonify
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)


@app.route("/")
def main():
    return "The python API is working!"

if __name__ == '__main__':
     app.run(debug=True)

So that is what we need to make it run, we import what we need, and then we create a main function that listens to our main-address and returns a text that our API is working. Last we have some code checking if our code is the main and if it is calling the app-run method with a debug flag.

However, we need some data source, and I have chosen MongoDB, so we going to add the pymogo connector to add and read some data.

So let's add a class for connection to Mongo, so I create a ClientFactory, it's not necessary to have one, but I like it.

so in our connectionFactory.py

from pymongo import MongoClient


class ConnectionFactory:
    def __init__(self, connection_string, database_name):
        self.connection_string = connection_string
        self.database_name = database_name

    def make(self):
        return MongoClient(self.connection_string)

Then I create another class, MongoConnection.py that inherit the connectionFactory.py

from ConnectionFactory import ConnectionFactory
from pymongo import MongoClient


class MongoConnection(ConnectionFactory):

    def make(self):
        client = MongoClient(self.connection_string)
        return client.get_database(self.database_name)

The way mongo work is that you connect to a server, then you tell what database you want, and then you call the collections that you want to work with. Since I maybe will use mongo by others API through this parts, I want to have a connection factory that gives me the specified database so that I only provide the different collections, not the database every time.

Now we need to modify our main.py so that we can use the MongoDB client.

So in main.py we add following:

import our connection class:

from MongoConnection import MongoConnection

then we add the properties we need to connect to mongo:

mongoConnectionString = "mongodb://localhost:27017/"
mongoDatabase = "schools_database"
mongoCollection = "schools"

client = MongoConnection(mongoConnectionString, mongoDatabase).make()

So now we have a client that can connect to mongo. However, how do we know it works even if we don't get any compilation errors? Let's add a function to the API and check I we got a connection.

@app.route("/mongo")
def mongo():
    return str(client.server_info)

if you now go to http://localhost:5000/mongo you should see:

Collection(Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'schools_database'), 'server_info')

So then we know it works!

But lets create a flask resful resource instead. i think its clearer and much nicer in code that way so lets change the code we wrote to:

class Schools(Resource):
    def get(self):
        documents = client.schools.find()
        return jsonify(schools=json.loads(json_util.dumps(documents)))

So we create a resource that we call Schools, we define it as an HTTP GET. In the GET function, we make a call and find all the documents for the collection schools. Then we return the result as JSON. In this part, we are not going to make thousands of entries, so we are fine with the find-call but keep in mind that you should page results with e.x take, skip, top, etc.

However, this calls is not going to get through, see that we do not have the app-route declaration?

@app.route("/schools")

It is because we are going to register it through the app instead. So after our school-resource, we are going to add this:

api.add_resource(Schools, '/schools')

Now the app knows that we want to route the incoming requests to the resource Schools and return the data from Mongo.

So let's try it out!

Well, you get nothing at all when going to http://localhost:5000/schools well, that's correct because we do not have any data in mongo. So we need to seed the database if we want to see some data.

Let's make a simple seed class that seeds the database with some hardcoded data, but only when it is empty.

So let's create a seed.py and put the following code in it:

from pymongo import MongoClient

def seed(client, collection):
    schools = client.get_collection(collection)

    school = {
  "name": "Stockholm university",
  "department": "Department of Computer and Systems Sciences",
  "contacts": {
    "telephone": {
      "switchboard": "+46-8-16 20 00",
      "studentAffairs": "+46-8-16 16 40"
    },
    "email": "studexp@dsv.su.se",
    "visitingaddress": {
      "street": "NOD-huset, Borgarfjordsgatan 12",
      "floor": "The Student Center, floor 1 elevator E",
      "zipcode": "SE-164 55 KISTA"
    },
    "postaladdress": {
      "name": "Department of Computer and Systems Sciences, Stockholm University",
      "streetadress": "NOD-huset, Borgarfjordsgatan 12",
      "zipcode": "SE-164 55 KISTA"
    }
  }
}
    school2 = {
  "name": "Stockholm university",
  "department": "Department of Political Science",
  "contacts": {
    "telephone": {
      "switchboard": "+46-8-16 20 00",
      "studentAffairs": "+46-8-16 49 07"
    },
    "email": "studentexpedition@statsvet.su.se",
    "visitingaddress": {
      "street": "Universitetsvägen 10 F",
      "floor": "F-building floors 4, 5 and 7, and Skogstorpet",
      "zipcode": "SE-171 76 STOCKHOLM"
    },
    "postaladdress": {
      "name": "Department of Political Science, Stockholm University",
      "street": "Universitetsvägen 10 F",
      "zipcode": "SE-171 76 STOCKHOLM"
    }
  }
}

    school3 = {
  "name": "Stockholm university",
  "department": "Department of Psychology",
  "contacts": {
    "telephone": {
      "switchboard": "+46-8-16 20 00",
      "studentAffairs": "+46-8-16 38 03"
    },
    "email": "hakan.fischer@psychology.su.se",
    "visitingaddress": {
      "street": "Frescati hagväg 8-14",
      "floor": "",
      "zipcode": "SE-106 91 STOCKHOLM"
    },
    "postaladdress": {
      "name": "Department of Psychology, Stockholm University",
      "street": "Frescati hagväg 8-14",
      "zipcode": "SE-106 91 STOCKHOLM"
    }
  }
}

    return schools.insert_many([school, school2, school3]).inserted_ids

Since we already created a client in main why not use the same connection. Since we want to seed the data if the database is empty, we need a function to check if it has data or not.

So lets add the following befor our seed function:

def is_seeded(client, collection):
    collection = client.get_collection(collection)
    return collection.count_documents({}) > 0

So let's go back to our main.py and add the check to our if our seed function should fire our not. Just after this:

client = MongoConnection(mongoConnectionString, mongoDatabase).make()

we add:

if(is_seeded(client, mongoCollection) == False):
    seed(client, mongoCollection)

main.py should now look like this:

from flask import Flask, jsonify
from flask_restful import Resource, Api
from bson import json_util, ObjectId
from MongoConnection import MongoConnection
from seed import seed, is_seeded

import json

app = Flask(__name__)
api = Api(app)

mongoConnectionString = "mongodb://localhost:27017/"
mongoDatabase = "schools_database"
mongoCollection = "schools"

client = MongoConnection(mongoConnectionString, mongoDatabase).make()

if(is_seeded(client, mongoCollection) == False):
    seed(client, mongoCollection)


@app.route("/")
def main():
    return "The python API is working!"


class Schools(Resource):
    def get(self):
        documents = client.schools.find()
        return jsonify(schools=json.loads(json_util.dumps(documents)))


api.add_resource(Schools, '/schools')

if __name__ == '__main__':
    app.run(debug=True)

Now we have a working seed function, the easiest way to test this out is to fire the application two times and then look in Robomongo and see if we get duplicates.

Now we got the first API done, we probably need to go back and add something, but at least we have a working API. In the next part, we are building the dotnet core 2 API.