Create an SQL Database
Additionally, we need to create a database, and for that, we have to save the following script in the project directory and run it with Python. We can also use the interactive interpreter too:
import sqlite3
conn = sqlite3.connect('todo.db') # Warning: This file is created in the current directory
conn.execute("CREATE TABLE todo (id INTEGER PRIMARY KEY, task char(100) NOT NULL, status bool NOT NULL)")
conn.execute("INSERT INTO todo (task,status) VALUES ('Read A-byte-of-python to get a good introduction into Python',0)")
conn.execute("INSERT INTO todo (task,status) VALUES ('Visit the Python website',1)")
conn.execute("INSERT INTO todo (task,status) VALUES ('Test various editors for and check the syntax highlighting',1)")
conn.execute("INSERT INTO todo (task,status) VALUES ('Choose your favorite WSGI-Framework',0)")
conn.commit()

You can also try this code with Online Python Compiler
Run Code
This will generate a database file todo.db along with tables called todo and three columns id status, and task. ID is a distinctive ID for each row, which will be later used in the reference of the rows. The task column holds the text which describes the task, and it can be a maximum of 100 characters. and at last, the column status is used for marking a task as open or closed.
You can also practice with the help of Online Python Compiler
Using Bottle For a web-based Todo List
Now, we will introduce Bottle to a web-based application. So first, we will be looking into a basic concept of Bottle's routes. Each page we see in the browser is dynamically generated when the address is called. Therefore, there is no static content available. And This is what exactly we call a "route" in Bottle. The route is basically a specific address on the server.
✍️Step1 Show All open items
After understanding what a route is, let's create our first route. The main objective is to see all the open items from the ToDo list:
import sqlite3
from bottle import run
from bottle import route
@route('/todo')
def todo_list():
connection = sqlite3.connect('todo.db')
conn = connection.cursor()
conn.execute("SELECT id, task FROM todo WHERE status LIKE '1'")
result = conn.fetchall()
return str(result)
run()

You can also try this code with Online Python Compiler
Run Code
Now save this code as todo.py in the same directory as the file todo.db. The other way is to add the path to todo.db in the sqlite3.connect() statement.
We imported the necessary module sqlite3 to access the SQLite database, and we imported the route and run from Bottle. The run() statement here will start the web server included in Bottle. It serves the pages on localhost and port 8080 by default. In addition to this, we imported a route, which is responsible for Bottle's routing. As we can see, we have defined one function, todo_list(), with a few lines of code reading from the database. The important statement here is the decorator statement @route('/todo') just before the deg todo_list() statement. By executing this, we are binding this function to the route /todo, so every time we call http://localhost:8080/todo, Bottle will return the function todo_list(). This is how routing within the Bottle works.
We can bind multiple routes to a function. So the below code will run fine too:
@route('/todo')
@route('/my_todo_list')
def todo_list():
...

You can also try this code with Online Python Compiler
Run Code
Whatever we will see on the browser is what is returned, and it is given by the return statement. In this example, we will convert the result into a string by str(), as the Bottle expects a string or List of strings from the return statement. Now it's time to execute. As we know from earlier that on Linux/Unix-based systems, we need to execute the todo.py file first. So, we will run python todo.py and call the page http://localhost:8080/todo in our browser. If we have done everything right, then the following output will be generated:
[(2, visit the Python website'), (3, u'Test various editors for and check the syntax highlighting')]
✍️Debugger and Auto-Reload
As we already know, the Bottle sends an error message to the browser in case something is wrong in the script, e.g., the connection to the database is not working. To debug, it is helpful to get more details. To achieve that easily, we need to add the following statement to the script:
from bottle import run, route, debug
...
#add this at the very end:
debug(True)
run()

You can also try this code with Online Python Compiler
Run Code
By enabling "debug," we will get a full stacktrace of the Python interpreter, which contains helpful information for finding bugs. In addition, templates are not cached; therefore, changes to templates will be taking effect without stopping the server. Another amazing feature is auto-reloading, which will be enabled by modifying the run() statement:
run(reloader=True)
This will automatically reload the new version when it detects changes to the script, without the need to stop the server or start the server.
✍️Bottle Template To Format The Output
Now we will try to cast the output of the script into a proper format. The Bottle comes up with its easy-to-use template engine with it. Templates are contained as separate files having a .tpl extension. The templates can be called within a function. Templates are allowed to contain any type of text.
Moreover, Templates can take arguments. Here, We will cast out the result of our query showing the open ToDo items into a simple table within two columns: the first will store the ID of the item, and the second will contain the text. As we have seen earlier, a set is a list of tuples, and each table will contain one set of results. Loading the template in our current example, we need to add a few lines to the code; the lines are as follows:
from bottle import route, run, debug, template
...
result = c.fetchall()
c.close()
output = template('make_table', rows=result)
return output
…

You can also try this code with Online Python Compiler
Run Code
Two things are done here; first, we have imported the template from the Bottle to use the template's full stop, and second, we have assigned the template make_table 2D variable output, which is returned. In the template, we have assigned the resulting coma we received from the database query e to the variable rows, which we will use afterward within the template.
Returns a list of strings; therefore, there is no need to convert anything. We can shorten one line of code by writing a return template ('make_table, 'rows=result), giving the same output as above.
The below code is the corresponding template:
%#template to generate an HTML
%# table from a list of tuples
%# (or List of lists, or tuple of tuples, or ...)
<p>The open items are as follows:</p>
<table border="1">
%for row in rows:
<tr>
%for col in a row:
<td>{{col}}</td>
%end
</tr>
%end
</table>
Above code make_table.tpl within the same directory as todo.py is stored. Each line that is starting with %l is interpreted as python code. The templates will raise exceptions just as any python code. The remaining lines are plain HTML markups. Now, if you run Again The Script and look at the output, it is still not nice but at least more readable.
✍️Using GET and POST values
Step we are adding new items to the to-do list. The item must be received from a basic HTML-based form that will send its data by the GET method. For doing so, we need to add a route to our script and tell the route that it should get GET data:
from bottle import run, route, debug, template, request
...
return template('make_table', rows=result)
...
@route('/new', method='GET')
def new_item():
new = request.GET.task.strip()
connection = sqlite3.connect('todo.db')
conn = connection.cursor()
conn.execute("INSERT into todo (task,status) VALUES (?,?)", (new, 1))
new_id = conn.lastrowid
connection.commit()
conn.close()
return '<p>The new task is inserted into the database and the ID is %s</p>' % new_id

You can also try this code with Online Python Compiler
Run Code
To access GET/POST data, we must import a request from Bottle. We will use the statement request to assign the actual data to a variable.GET.task.strip() statement, in which task is the name of the GET data we want to retrieve. The remaining code is just processing the gained data: writing to the database, retrieving the correspondence id from the database, and generating the output.
We can use a static HTML page holding the form to get the GET data form. And the other way is to use a template that is output when the route /new is called without GET data.
The code needed to be extended is as follows:
...
@route('/new', method='GET')
def new_item():
if request.GET.save:
new = request.GET.task.strip()
connection = sqlite3.connect('todo.db')
conn = connection.cursor()
conn.execute("INSERT into todo (task,status) VALUES (?,?)", (new,1))
new_id = conn.lastrowid
connection.commit()
conn.close()
return '<p>The new task is inserted into the database and the ID is %s</p>' % new_id
else:
return template('new_task.tpl')
new_task.tpl will be as follows:
<p>Add new task to the ToDo list:</p>
<form action="/new" method="GET">
<input type="text" maxlength="100" size="100" name="task">
<input type="submit" name="save" value="save">
</form>

You can also try this code with Online Python Compiler
Run Code
Now, like this, our to-do list is extended. And for doing the same by POST data, we just have to use request.POST.get() instead.
✍️Editing Existing items
Here, we will see the concept of "dynamic routes," which will simplify the task of existing items.
The basic statement used for a dynamic route is as follows:
@route('/myroute/<something>')
The above statement tells Bottle to accept for <something> any string upto the next slash. Moreover, the value present in something will be passed to the function that is assigned to the route, So data will be processed within the function as follows:
@route('/edit/<no:int>', method='GET')
def edit_item(no):
if request.GET.save:
status = request.GET.status.strip()
edit = request.GET.task.strip()
if status == "open":
status = 1
else:
status = 0
connection = sqlite3.connect('todo.db')
conn = conn.cursor()
conn.execute("UPDATE todo SET task = ?, status = ? where id LIKE ?", (edit, status, no))
connection.commit()
return '<p>The item number %s is successfully updated</p>' % no
else:
connection = sqlite3.connect('todo.db')
conn = con.cursor()
conn.execute("SELECT task from todo WHERE id LIKE ?", (str(no),))
cur_data = conn.fetchone()
return template('edit_task', old=cur_data, no=no)

You can also try this code with Online Python Compiler
Run Code
This is the same as we did above; the addition uses the dynamic route <no:int>, which passes the number to the respective function.
The template edit_task.tpl called within the function will look like the below code:
%#To edit a task,
%#the template expects a value for "old"
%# as well as "no," the text
%# of the selected ToDo item.
<p>Edit the task with the task ID = {{no}}</p>
<form action="/edit/{{no}}" method="get">
<input type="text" value="{{old[0]}}" name="task" size="100" maxlength="100">
<select name="status">
<option>open</option>
<option>closed</option>
</select>
<br>
<input type="submit" name="save" value="save">
</form>
Note: We can also use a regular expression for a dynamic route.
✍️Validating Dynamic Routes
It is good to use dynamic routes, but in many cases, it is logical to validate the dynamic part of the route. If we take an example, we are expecting an integer number in our route for editing. But if a float, character, or something else is received, This will throw an exception, which is what we don't want. And for this case, Bottle offers the <name:int> wildcard filter, which checks similar digits and converts the value to an integer. For applying the wildcard filter, we will expand the code as follows:
from bottle import run,routes, debug, template, request
...
@route('/edit/<no:int>', method='GET')
def edit_item(no):
...

You can also try this code with Online Python Compiler
Run Code
After saving the code, call the page again using the wrong value for <no:int>, for example, a float. We will receive an exception but a "404 Not Found" error.
✍️Dynamic Routes Using Regular Expressions
To demonstrate that Botle can also handle dynamic routes, where the "dynamic part" of the route can be a regular expression, let us assume that all the single items in our ToDo list must be accessible by their plain number. And we don't want to create a route for every item. And the solution to this is a regular expression.
@route('/item<item:re:[0-9]+>')
def show_item(item):
con = sqlite3.connect('todo.db')
conn = con.cursor()
conn.execute("SELECT task FROM todo WHERE id LIKE ?", (item,))
res = conn.fetchall()
conn.close()
if not res:
return 'This item number does not exist!'
else:
return 'Task: %s' % res[0]

You can also try this code with Online Python Compiler
Run Code
The line @route(/itemitem:re:[0-9]+>) will begin like a conventional route, but the dynamic component of the route is the third part of the wildcard, which is treated as a regular expression. So the following function "show_item" simply checks whether the given item is present in the database or not.
✍️Returning Static Files
Sometimes it becomes necessary to associate a route, not to a Python function, But simply to return a static file. So if we have a help page for our application, we may want to return this page as plain HTML. This will be done as follows:
from bottle import route, debug, template, run, static_file, request
@route('/help')
def helper():
return static_file('helper.html', root='/path/to/file')

You can also try this code with Online Python Compiler
Run Code
Here, we first need to import the static_file function from Bottle. As we can see, the return static_file statement replaces the return statement. It takes two arguments, at least the first the name of the file to be returned and the second the path to the file. If this helper.html file is in the same folder/directory as of application, we need to state the path.
✍️Returning JSON Data
In some cases, we would prefer our app to return data instead of the output so that it can be processed further. for example; it is by Javascript. For that scenario, Bottle offers the possibility to return JSON objects, which is standard for exchanging data between web applications.
So, suppose we want to return the data generated in the regular expression route. The code for that is as follows:
@route('/json<json:re:[0-9]+>')
def show_json(json):
con = sqlite3.connect('todo.db')
conn = con.cursor()
conn.execute("SELECT task FROM todo WHERE id LIKE ?", (json,))
result = conn.fetchall()
conn.close()
if not result:
return {'task': 'This item number does not exist!'}
else:
return {'task': result[0]}

You can also try this code with Online Python Compiler
Run Code
As we can see in the code, it has simply returned a regular Python Dictionary, and Bottle will convert it into a JSON object automatically before sending. So when we call "http://localhost/json1," the Bottle should return the JSON object in this case, i.e. {"task": ["Read A-byte-of-python to get a good introduction into Python"]}.
✍️Catching Errors
In this step, we need to catch an error with the Bottle to keep away any error message from the user of our application. For doing that, Bottle has an "error route," which can be assigned to an HTML error.
In this case, we want to catch a 403 error. The code for this is as follows:
from bottle import error
@error(403)
def mistake(code):
return 'The parameter you passed has the wrong format!'

You can also try this code with Online Python Compiler
Run Code
So, firstly we need to import the error from Bottle and define a route by error(403), which will catch all the "403 forbidden errors". The method mistake is assigned to that. The point to be noted here is that error() always passes the error code to the method, even if we don't need it. Therefore, the function must always accept one argument, or it will not work.
Here, we can assign more than one error route to a function. The code for that is as follows:
@error(404)
@error(403)
def mistake(code):
return 'There is something wrong!'
The below code will also be working fine:
@error(403)
def error403(code):
#return error message
return 'The parameter you passed has the wrong format!'
#error404
@error(404)
def error404(code):
return 'Sorry, this page does not exist!'

You can also try this code with Online Python Compiler
Run Code
Server Setup
Until now, we have used the standard server used by Bottle, that is, WSGI reference Server shipped along with Python. Though this server is suitable for development purposes, it is not suitable for larger applications. So in this section, we will look for alternatives, but before that, let's see how to tweak the standard server settings first.
✍️Running Bottle on a different port and IP
Bottle servers the page on IP address 127.0.0.1, also called localhost, and on port 8080. To change the setting is quite simple, as additional parameters can be passed to Bottle's run() function to change the address and the port.
For changing the port, we just need to add port=portnumber to the run command. So if run(port=80) makes Bottle listen to port 80. Then for changing the IP address, we need to follow the command:
run(host='123.45.67.89')
If required, we can combine both the parameters as follows:
run(port=80, host='123.45.67.89')
These parameters can also be applied when Bottle is running with a different server; we will see that in the following section.
✍️Running Bottle with a different server
The Bottle already has different adapters to multi-thread servers on board, which perform better on larger boards. Bottle supports Cherrypy, Paste, and Flip. We can run Bottle with the paste server by using the following code:
from bottle import PasteServer
...
run(server=PasteServer)

You can also try this code with Online Python Compiler
Run Code
This code will work exactly like with CherryPyserver, FlupServer, and FapwSerever.
Also read, Fibonacci Series in Python
Complete Example
📌Main code for the app todo.py:
import sqlite3
from bottle import route, debug, run, template, static_file, error, request
# only required when you run Bottle on mod_wsgi
from bottle import default_app
@route('/todo')
def todo_list():
conn = sqlite3.connect('todo.db')
c = conn.cursor()
c.execute("SELECT id, task FROM todo WHERE status LIKE '1'")
result = c.fetchall()
c.close()
output = template('make_table', rows=result)
return output
@route('/new', method='GET')
def new_item():
if request.GET.save:
new = request.GET.task.strip()
con = sqlite3.connect('todo.db')
conn = conn.cursor()
conn.execute("INSERT INTO todo (task,status) VALUES (?,?)", (new, 1))
new_id = conn.lastrowid
con.commit()
conn.close()
return '<p>The new task was inserted into the database, the ID is %s</p>' % new_id
else:
return template('new_task.tpl')
@route('/edit/<no:int>', method='GET')
def edit_item(no):
if request.GET.save:
edit = request.GET.task.strip()
status = request.GET.status.strip()
if status == 'open':
%# change status value to 1
status = 1
%# else make the status value 0.
else:
# change status value to 1
status = 0
con = sqlite3.connect('todo.db')
conn = conn.cursor()
conn.execute("UPDATE todo SET task = ?, status = ? WHERE id LIKE ?", (edit, status, no))
con.commit()
return '<p>The item number %s was successfully updated</p>' % no
else:
con = sqlite3.connect('todo.db')
conn = con.cursor()
conn.execute("SELECT task FROM todo WHERE id LIKE ?", (str(no)))
cur_data = conn.fetchone()
return template('edit_task', old=cur_data, no=no)
@route('/item<item:re:[0-9]+>')
def show_item(item):
con = sqlite3.connect('todo.db')
conn = con.cursor()
conn.execute("SELECT task FROM todo WHERE id LIKE ?", (item,))
result = conn.fetchall()
conn.close()
if not result:
return 'This item number does not exist!'
else:
return 'Task: %s' % result[0]
@route('/help')
def help():
static_file('help.html', root='.')
@route('/json<json:re:[0-9]+>')
def show_json(json):
con = sqlite3.connect('todo.db')
conn = con.cursor()
conn.execute("SELECT task FROM todo WHERE id LIKE ?", (json,))
result = conn.fetchall()
conn.close()
if not result:
%#return message
return {'task': 'This item number does not exist!'}
%# return task
else:
return {'task': result[0]}
@error(403)
def error403(code):
return 'There is a mistake in your URL!'
@error(404)
def mistake404(code):
return 'Sorry, this page does not exist!'
debug(True)
run(reloader=True)
%# remember to remove the reloader=True
%# and debug(True) when you move your
#% application from development to a productive environment

You can also try this code with Online Python Compiler
Run Code
📌Template make_table.tpl:
%#template to generate an HTML table
%# from a list of tuples
%#(or List of lists, or tuple of tuples, or ...)
<p>The open items are as follows:</p>
<table border="1">
%for row in rows:
<tr>
%for col in row:
<td>{{col}}</td>
%end
</tr>
%end
</table>
📌Template edit_task.tpl:
%#template to edit a task task
%#the template expects
%#a value for "old" as well
%# as a "no," the text of the selected ToDo item
%#add form template
<p>Edit the task with ID = {{no}}</p>
%# take form inputs
<form action="/edit/{{no}}" method="get">
<input type="text" name="task" value="{{old[0]}}" size="100" maxlength="100">
<select name="status">
<option>open</option>
<option>closed</option>
</select>
<br>
<input type="submit" name="save" value="save">
</form>
📌 Template new_task.tpl:
%#template for the %#form for a new task
<p>Add a new task to the ToDo list:</p>
<form action="/new" method="GET">
<input type="text" size="100" maxlength="100" name="task">
<input type="submit" name="save" value="save">
</form>
Frequently Asked Questions
What does WSGI stand for?
The Web Server Gateway Interface (pronounced whiskey or WIZ-ghee) is a straightforward calling standard used by web servers to route requests to web applications or frameworks created in the Python programming language.
Is Apache a WSGI?
A Python application is embedded within the Apache HTTP Server using the mod WSGI module, which enables communication via the Python WSGI interface as specified in Python PEP 333. One Python method for creating high-quality, high-performance web apps is WSGI.
Describe Django and the Bottle.
Model-template-view (MTV) is the basis for its design. It includes many tools that application developers require, including an ORM framework, admin panel, directory structure, and more.
What do you understand about the bottle web framework?
The Bottle is a Python WSGI micro web framework that is quick, easy, and lightweight. It is supplied as a single file module and only requires the Python Standard Library as a dependency.
Is REST API a framework?
The REST API is nothing but a part of the framework that handles requests from external consumers.
Conclusion
This article discussed how to create a Todo application using Bottle Framework. We started with the introduction of the bottle framework, and then we saw Goals, Server setup, and a complete Example.
To learn more about Bottle Web Framework, see Bottle Documentation, Bottle, and Python Frameworks.
Refer to our guided paths on Coding Ninjas Studio to learn more about DSA, Competitive Programming, JavaScript, System Design, etc. Enroll in our courses, refer to the mock test and problems; look at the interview experiences and interview bundle for placement preparations.
Happy Learning, Ninjas!