Goal
Implement the simple API from the previous article A simple API.
Contents
- Goal
- Contents
- Project Log
Project Log
Excerpt from:
A simple API
API: Helloworld
Version: 1
Author: StJohn Piano
Date: 2019-04-02
/api/v1/hello
result: "world"
/api/v1/[anything_else]
result: "error"
An API maps URIs to functions. These functions are executed and the results are returned to the program that passed a URI (perhaps with some data) to the API.
Some of the functions may alter stored data. Data is stored in a database. A database can be a complete table manager application, or simply some text files in a filesystem.
API = Application Programming Interface
URI = Uniform Resource Identifier
System details:
- Name: Shovel
- Specifications: HP 6005 Pro SFF. 3 GHz x86_64 processor (AMD II x4 B95 Quad Core), 4 GB RAM, 1 TB hard drive. Running CentOS 7.6.1810 (Core).
- More information: New computer: Shovel
- Installed items: GCC 4.8.5, Make 3.82, Vim 7.4, Python 2.7.5, Python 3.3.2.
I'll implement this API in Python 2.7.5.
This implementation will consist of:
- a file api.py that:
-- accepts a URI and maps it to a function
-- various functions
- a file app.py that:
-- creates a URI, passes it to api.py, waits for the result, and prints the result.
Hm. On second thought, I'll split the URI mapping and the functions. In any medium-sized API it will be easier to have these in two separate files.
So:
This implementation will consist of:
- a file api.py that:
-- accepts a URI and maps it to a function
- a file functions.py that:
-- contains various functions
- a file app.py that:
-- creates a URI, passes it to api.py, waits for the result, and prints the result.
I think it might be easier to consider the API concept to only consist of the URI-function mapping, and not to include the functions themselves.
I'm using the Vim editor.
Reference article:
Basic Vim commands
Create a new project directory named "a_simple_api_implementation". Within it, create a new work directory named "work".
Open a terminal and change directory to the work directory.
Using Vim, write the three code files api.py, functions.py, and app.py.
[spiano@localhost work]$ which python
/usr/bin/python
[development occurs here]
Ok. Done. Let's test:
[spiano@localhost work]$ python app.py
world
Peachy.
Here is the code:
app.py
#!/usr/bin/python | |
import api | |
def main(): | |
uri = "/hello" | |
result = api.process_uri(uri) | |
print result | |
if __name__ == '__main__': | |
main() |
api.py
import functions as ff | |
def process_uri(uri): | |
success = False # if the uri is successfully parsed, this will be changed to True. | |
result = None | |
# example uri: /hello | |
# first character must be "/" | |
if uri[0] != '/': | |
raise Exception('first character in URI must be {/}') | |
uri_2 = uri[1:] # remove first character | |
parts = uri_2.split('/') | |
if len(parts) == 1: | |
if parts[0] == "hello": | |
success = True | |
result = ff.hello() | |
# if the uri has not been successfully parsed, then return an error. | |
if success == False: | |
result = "error" | |
return result |
functions.py
def hello(): | |
return "world" |
In this implementation, the bulk of the complexity happens within api.py. However, in practice, the difficulty and complexity would shift to functions.py.
There would probably also be an ORM (Object-Relational Mapper) between functions.py and the database. An ORM translates between the object structures used in the code and the table structures used within the database.
Let's test that this API returns an error if a different URI is passed into it.
Change app.py:
app.py
#!/usr/bin/python | |
import api | |
def main(): | |
uri = "/hola" | |
result = api.process_uri(uri) | |
print result | |
if __name__ == '__main__': | |
main() |
[spiano@localhost work]$ python app.py
error
Good.
Now expand the codebase so that this is version 1 of the API. I'll create the new file v1_api.py and rename functions.py to v1_functions.py. The file api.py will now import the file v1_api.py and pass any uri that begins with {/api/v1} to it.
The "v1_" prefix for the files ensures that, in most filesystems, the files for a particular version will be grouped together in the file list for the directory.
The file api.py will now primarily route URIs to particular api versions (i.e. v1_api.py etc).
[development occurs here]
Ok. Done. Let's test:
[spiano@localhost work]$ python app.py
world
Good.
Here's the code:
app.py
#!/usr/bin/python | |
import api | |
def main(): | |
uri = "/api/v1/hello" | |
result = api.process_uri(uri) | |
print result | |
if __name__ == '__main__': | |
main() |
api.py
import v1_api | |
def process_uri(uri): | |
success = False # if the uri is successfully parsed, this will be changed to True. | |
result = None | |
# example uri: /api/v1/hello | |
# first character must be "/" | |
if uri[0] != '/': | |
raise Exception('first character in URI must be {/}') | |
uri_2 = uri[1:] # remove first character | |
parts = uri_2.split('/') | |
if len(parts) >= 2: | |
if parts[0] == "api": | |
if parts[1] == "v1": | |
parts_2 = parts[2:] # remove /api/v1 from the uri | |
uri_3 = "/" + "/".join(parts_2) # construct uri from parts | |
success = True # this file has successfully parsed the section of the uri in which it was interested. the v1_api may return an error, but that's a separate concern. | |
result = v1_api.process_uri(uri_3) | |
# if the uri has not been successfully parsed, then return an error. | |
if success == False: | |
result = "error" | |
return result |
v1_api.py
import v1_functions as ff | |
def process_uri(uri): | |
success = False # if the uri is successfully parsed, this will be changed to True. | |
result = None | |
# example uri: /hello | |
# first character must be "/" | |
if uri[0] != '/': | |
raise Exception('first character in URI must be {/}') | |
uri_2 = uri[1:] # remove first character | |
parts = uri_2.split('/') | |
if len(parts) == 1: | |
if parts[0] == "hello": | |
success = True | |
result = ff.hello() | |
# if the uri has not been successfully parsed, then return an error. | |
if success == False: | |
result = "error" | |
return result |
v1_functions.py
def hello(): | |
return "world" |
Let's test that this API returns an error if a different URI is passed into it.
Change app.py:
app.py
#!/usr/bin/python | |
import api | |
def main(): | |
uri = "/api/v1/hola" | |
result = api.process_uri(uri) | |
print result | |
if __name__ == '__main__': | |
main() |
[spiano@localhost work]$ python app.py
error
Great.
Ok. That's the end of this project.