Making and packaging a fake (for now) geocode() function

We might not know how to write a geocoder yet, but we can at least fake it with some fake data and then package it in the Python way.
This article is part of a sequence.
Project Example: Show Me Nearest Earthquakes
A step-by-step process of creating a project that downloads earthquake data, geocodes a user's location, and displays the nearest locations.

Summary

This lesson doesn’t involve writing a ton of new code, but we learn how to just draft out code that we complete later. And then, how to organize it within a project so that all the code isn’t in a giant mess of a file.

Table of contents

Getting started

This exercise assumes you've completed the previous exercise, in which you've created a basic woz.py file in your projects/show-me-where folder:

    compciv-2016
    └── projects
        └── show-me-where
            └── woz.py

You should still have a Terminal window open to your system shell, at your show-me-where subdirectory, i.e., this should still work:

$ python woz.py

Create geocoding.py

For this phase, we'll start out by creating a new file in show-me-where/: geocoding.py

    compciv-2016
    └── projects
        └── show-me-where
            └── geocoding.py
            └── woz.py

In a later exercise, we'll actually make it do something with a real-world API. But for this exercise, we'll "mock" it out. No matter what the user inputs into the function, the geocode command will always print out a pre-cannned response.

What does our (fake) geocoder do?

Nothing much. It's not really "fake" so much as a "placeholder". We know that our main program, woz.py, will eventually call a geocode() function that takes in an argument – a location string from the user – and then return geodata from Mapzen, e.g. latitude and longitude.

But we'll do the actual geocoding in a later lesson. So we'll just write the code to respond with a "dummy string" for now.

Adding a "geocode" option in woz.py

In woz.py, look for this code:

elif the_command == 'geocode':
    print("TODO: geocode it")

We want this part of the program to accept user input, similar to "hello":

if the_command == "hello":
    usertext = input("What is your name? ")

Except, we want to ask for the user's location. So refer to the "hello" snippet above, and change it accordingly. Here's one way to do it:

elif the_command == 'geocode':
    userlocation = input("What is your location? ")
    print("OK...geocoding:", userlocation)
    print("""
    {
      "latitude": 99,
      "longitude": -42
    }
    """)    

Now run woz.py from the command line:

$ python woz.py
What do you want to do? geocode
What is your location? Somewhere
OK...geocoding: Somewhere

    {
      "latitude": 99,
      "longitude": -42
    }

That dictionary/JSON-formatted text is just dummy text, of course. But it works for now.

geocoding.py

So this is a bit of software engineering and Python organization.

We don't want to do our geocoding inside of woz.py – because things would get very cluttered. Instead, we want to "package" a geocode() function inside of a different file.

If you haven't done it already, create a new file named geocoding.py and, for now, put it in the current folder with woz.py:

    compciv-2016
    └── projects
        └── show-me-where
            └── geocoding.py
            └── woz.py

Now, inside geocoding.py, create a function named geocode. For now, it can just print the same dummy text as woz.py did, in our previous example:

def geocode(location):
    return ("""
    {
      "latitude": 99,
      "longitude": -42
    }
    """)

(again, the above snippet should be in geocoding.py__)

Add an import statement: import geocoding

OK, now switch back to woz.py, and now add an import statement at the top of woz.py:

import geocoding

Now, further down in the code, change this section:

    elif the_command == 'geocode':
        userlocation = input("What is your location? ")
        print("OK...geocoding:", userlocation)
        print("""
        {
          "latitude": 99,
          "longitude": -42
        }
        """)

To:

    elif the_command == 'geocode':
        userlocation = input("What is your location? ")
        print("OK...geocoding:", userlocation)
        georesult = geocoding.geocode(userlocation)
        print(georesult)

The result should be the same; all we've done is move the fake functionality into a separate file and function.

Creating a subfolder of utilities

This is where things stand:

    compciv-2016
    └── projects
        └── show-me-where
            └── geocoding.py
            └── woz.py

Moving geocoding.py into the utils folder

Things get a little messy when you throw a ton of files all in the same subfolder. Let's let woz.py stand-alone for now. Which we can do by making a subfolder named utils:

    compciv-2016
    └── projects
        └── show-me-where
            └── geocoding.py
            └── woz.py
            └── utils

Then, move geocoding.py into utils. In other words, it should no longer be at the same level as woz.py. Move it into utils:

    compciv-2016
    └── projects
        └── show-me-where
            └── woz.py
            └── utils
                └── geocoding.py

Creating utils/__init__.py

This is going to seem like the most arbitrary thing. Basically, we want to make the utils subfolder a package of files. And the way to do that is to create an empty file in utils named __init__.py: note the exact naming of the file, with double underscores before and after init:

    compciv-2016
    └── projects
        └── show-me-where
            └── woz.py
            └── utils
                └── geocoding.py
                └── __init__.py

Changing the import statement in woz.py

One more maintenance thing.

In woz.py, we had this import statement:

import geocoding

But that won't work anymore because geocoding.py is no longer in the same folder relative to woz.py

Now that geocoding.py has been moved into a subfolder, utils, we just have to change the import target:

from utils import geocoding

i.e. "From the utils folder, import the geocoding.py file"

When you run woz.py from the command line and say you want to "geocode", everything should work just as before.

Moving on

And that's it. You've created a slightly more organized Python project by creating a package (which involves making that utils/__init__.py file).

Remember all those times we've written this at the beginning of our scripts:

from os.path import join

Those were Python packages, too. Python packages let's us separate and organize code in different files, calling it as we need it in other files. Seems like a bit over-the-top right now, but that's only because we haven't written much code yet.

This article is part of a sequence.
Project Example: Show Me Nearest Earthquakes
A step-by-step process of creating a project that downloads earthquake data, geocodes a user's location, and displays the nearest locations.

References and Related Readings

Python Modules: Packages