Storing and Communicating Data
00:00 In the previous lessons, you’ve completed a way to create, initialize, and connect to the to-do database. In this lesson, you dive into the data model. Here you start to think about how to represent and store the to-do data and also build a system or connection to communicate between the CLI and the database.
00:22
First is to understand how you structure a to-do item. A to-do item will contain three main values, a description which will be a text value or series of strings, a priority value, which will be an integer value between one and three, and a done value, which will be a Boolean True
for when completed and False
for when not completed.
00:46 To communicate with a CLI at any given point in time in a subclass of a named tuple, you use two pieces of data to hold the acquired information of a to-do: first, the to-do itself, and you use a Python dictionary holding the information for the current to-do.
01:02 And second is an error code, the return or error code confirming if the current operation was successful or not. And all of this data is stored in a subclass of a named tuple with appropriately named fields.
01:18
Named tuples are specialized versions of tuples or subclasses of tuples provided by the collections
module in Python that enables you to create immutable objects with named attributes.
01:30 With named tuples, you can access its values using descriptive field names and the dot notation.
01:37
Now back in your code editor in your rp_todo
package, open the rp_todo.py
module. First, you import some required objects from typing
.
01:49
You’ll import Any
and NamedTuple
. This will be used for type notations later. And then from pathlib
import Path
.
02:01
Next, what you’re going to do is create the subclass of NamedTuple
to create an object called CurrentTodo
. This will represent the current to-do item in any given operation as you communicate with your CLI.
02:18 You’ve just seen Python subclasses in action. In Python, a subclass is a class that inherits the attributes and methods from another class known as a superclass or parent class.
02:30 With subclasses, you can reuse and extend the functionalities of the superclass, create specialized versions of the existing class, and override class methods to customize certain behaviors.
02:45
And this is Python subclassing in action. And just as discussed, the to-do field will hold a dictionary with keys of type str
and values can be of any data type, and then the error field will hold an integer value for the return or error code confirming if a current operation is successful or not.
03:07
The next thing you’ll create is a data container that will allow you to send and receive data from the database. And for this, you’ll use another NamedTuple
subclass.
03:18
So go ahead and open your database.py
module and from typing
import Any
and NamedTuple
. This will be used for type annotation.
03:29
Next, you define a NamedTuple
subclass called DBResponse
.
03:37
And you subclass NamedTuple
.
03:42
Then define two values: a list of to-dos called todo_list
, which will be type annotated as a list of dictionaries with keys that are str
and values of data type Any
, and then an error with integer values.
04:00 Now this to-do list will represent the list of to-dos you would use to write and hold the values when you read from a database, and the error will be an integer number representing a return code relating to the current database operation.
04:16
Next, you create something called a database handler. This is a class you’d use as a helper to read and write data to the to-do database. And to do that, under your DBResponse
NamedTuple
class, you create another class and call it DatabaseHandler
.
04:32
And with this, you define an initializer. What you’ll expect when you initialize an instance of this class is a db_path
, which is a type of Pathlib
Path
.
04:43
To differentiate this, you can use an .self
.db_path
, signifying that this is an internal variable.
04:52
Next, you’ll be creating two methods. One to read to-dos from a database and another to write to-dos to the database. So the first one will be the read_todos()
method, def read_todos(self)
, and the response will be a type DBResponse
, which has been defined above here.
05:12
And then in a try and except block, it catches an OSError
. And if an error occurs while trying to read to-do items from the database, you just return an instance of DBResponse
, but now you have no values to return.
05:26
So you return an empty list for the to-do list and a DB_READ_ERROR
. So ensure you imported that from rp_todo
and then return DB_READ_ERROR
.
05:39
Now in the main try block using context management with .self._db_path
, which is your database open method in read mode as db
, then you’re going to try to read the values from the file and return to the caller of this method.
05:58
And to do that, you’d use another try and except block. This time you’re reading a JSON file, so the error you can expect is a JSONDecodeError
.
06:07
At this point, you can import json
from the Python standard library and then catch that error. And if an error occurs while trying to read the JSON file, you’d return again an instance of DBResponse
since no file was read, return an empty list and JSON_ERROR
, again, import this from rp_todo
.
06:29
But if you’re able to read and decode the JSON file, you’d return that to the caller of this method, return an instance of DBResponse
, but now read the value using json.load()
returning the JSON file, which in the context management is referred to as db
and then the code would be SUCCESS
.
06:50
Great. You’ve just defined your read_todos()
method, which tries to parse the JSON file, read the data, and return to the caller whilst catching appropriate errors if they occur.
07:01
Next will be a method to write to-dos to the database. You define the method, write_todos()
, reference to class self
and this method takes a list of to-dos type annotated as lists with values that are dictionaries with key str
, and value of data type Any
.
07:23
This would return an instance of DBResponse
.
07:28
Also in a try and except block, this is a file manipulation, so it’d catch an OSError
that happens, return an instance of DBResponse
knowing there was an error, return back a to-do list path to be written alongside a DB_WRITE_ERROR
.
07:46
Back in the try block, using context management with with
statement, open up the database, which is the JSON file, using the open method in write mode this time. Reference the context as db
and in the context, what you’re going to do is write the list to the JSON file.
08:04
And to do that, you can use json.dump()
and what you’re dumping is the to-do list passed to the method, the file to write the data and the little styling indent 4
.
08:16
And if all this goes according to plan, you’d return, also give your response the to-do list passed and SUCCESS
. Wonderful. You’ve now created the write_todos()
method.
08:30
It takes a list of to-dos containing dictionaries of key strings and value of any data type and this will try to write those into the db
, which is your JSON file.
08:43
Now, to connect this database handler with your CLI application, you’d write another class. First, open the rptodo
file and here you’d create another class, the DatabaseHandler
with your CLI application.
08:58
This class will be called Todoer
. To create the connection, remember to import the DatabaseHandler
. From rptodo
database
import DatabaseHandler
, which you’ve just created in a database.py
module.
09:15 Then you go ahead to define the class initializer,
09:18
this reference self
. And for initialization, you’d require a db_path
, which would be a type of Pathlib
Path
, no values are returned.
09:29
Then you attach the DatabaseHandler
to this Todoer
class using method, .self.
internal method or internal object DatabaseHandler
equals reference the DatabaseHandler
you just imported from rptodo
database
module.
09:48
And this also requires a db_path
. So you pass in the db_path
gotten during the class or instance initialization. What you’ve just done in object-oriented programming is called class composition, where you’ve made the DatabaseHandler
class a part of the Todoer
class.
10:08 Class composition is a design principle where a class is built from other classes as components. Instead of inheriting from a parent class, a class has a relationship with other classes.
10:20 It represents a “has-a” or “part-of” relationship instead of inheritance’s “is-a” relationship. So unlike inheritance where you say a class is a type of another class, in composition, you just say a class is part of another class.
10:38 In this lesson, you put together a lot of things that shape how your to-do application’s backend will work. You started out with deciding what data structure you’d use to store your to-do item,
10:50 and now you’ve defined utility classes that will allow you to operate on your to-do database, which means you are almost ready to start receiving to-do items and populating them in your database.
Become a Member to join the conversation.