edgecase
Author: StJohn Piano
Published: 2019-04-03
Datafeed Article 96
This article has been digitally signed by Edgecase Datafeed.
This article has been digitally signed by its author.
1049 words - 537 lines - 14 pages





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

python 2.7.5
#!/usr/bin/python

import api 

def main():
    
    uri = "/hello"
    result = api.process_uri(uri)
    print result


if __name__ == '__main__':
    main()





api.py

python 2.7.5
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

python 2.7.5
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

python 2.7.5
#!/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

python 2.7.5
#!/usr/bin/python

import api

def main():
	
	uri = "/api/v1/hello"
	result = api.process_uri(uri)
	print result


if __name__ == '__main__':
	main()





api.py

python 2.7.5
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

python 2.7.5
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

python 2.7.5
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

python 2.7.5
#!/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.