- Implement a caching mechanism for assessment data in a web app.
- Modify existing methods to use cached data to improve performance.
- Test the caching mechanism to ensure the data is retrieved correctly.
- Address issues with adding or updating assessments, ensuring the cache is refreshed appropriately.
Now that we have cached the user data, our web app is fasters, but there are still some places where accessing the database slows it down.
If we look at the assessment_service we will notice two functions which retrieve data from the database:
- get_assessment
- get_chart
This implies that there are two more sets of data that we can cache. In this tutorial we will look to cache the get_assessment data, and we will deal with the get_chart data next tutorial.
The impact of using the get_assessment method is not as significant as get_user. It is only used in the HomeComponent. Never-the-less, this will be called every time the user goes to the HomeComponent, so it will still have an impact.
We will use the same process that we used for the get_user method:
- Create a variable to store assessment data in the data_access module.
- Create a frontend method that caches the assessment data if it is not currently cached, and then returns the cached data.
- Replace calls to get_assessment with calls to the new frontend method.
Lets get to it.
First we need to make a new variable to cache the assessment data.
- Open the data_access module,
- At the top, under the
cached values
section add the highlighted code
:linenos:
:lineno-start: 7
:emphasize-lines: 3
# cached values
__user = None
__assessments = None
:class: notice
- **line 9** → creates the private variable `__assessments` to store the assessment data
Now to create the method
- At the bottom of the data_access module add the following highlighted code.
:linenos:
:lineno-start: 35
:emphasize-lines: 1 - 10
def my_assessment():
global __assessments
if __assessments:
print("Using cached assessments")
return __assessments
print("Accessing assessments from database")
__assessments = anvil.server.call('get_assessment', user)
return __assessments
:class: notice
- **line 35** → creates the **my_assessment** method
- **line 36** → allows the method to edit the value of `__assessments`
- **line 38** → checks if the assessment data is already cached
- **line 39** → informs the developer that cached data is being used
- **line 40** → returns the cached data and ends the method
- **line 42** → informs the developed that the database is being accessed
- **line 43** → retrieves the assessment data from the database and caches it
- **line 44** → returned the cached data
Now we need to replace the calls to get_assessment
- Open HomeComponent in Code mode
- In the import section, add the highlighted code
:linenos:
:lineno-start: 1
:emphasize-lines: 8
from ._anvil_designer import HomeComponentTemplate
from anvil import *
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.users
from .. import data_access
:class: notice
- **line 8** → gives **HomeComponent** access to the **data_access** methods
- Insert the highlighted code at the bottom of the
__init__
method
:linenos:
:lineno-start: 11
:emphasize-lines: 6
def __init__(self, **properties):
# Set Form properties and Data Bindings.
self.init_components(**properties)
# Any code you write here will run before the form opens.
self.repeating_panel_1.items = data_access.my_assessment()
:class: notice
- **line 16** → uses our new **my_assessment** method to provide the data for the repeating panels.
Time to test your web site and see if our changes worked.
Testing:
- Launch your web app
- Wait for the Home page to load - this should take some time
- Go to the Account page - this should load instantly
- Go back to the Home page - this should load instantly
- Go to the Add page - this should load instantly
- Add a new assessment item
- Go back to the Home page
- Check if the assessment item is there - spoiler, it won't be
The problem with adding assessment, is similar to the one we had with changing user data. When we add a new assessment, it gets written to the database, but the cached assessment data does not change. We need to fix this.
- Open the data_access module
- Insert the highlighted code into the bottom of the module
:linenos:
:lineno-start: 46
:emphasize-lines: 1 - 7
def add_assessment(subject, details, start_date, due_date):
global __assessments
print("Writing assessment details to the database")
anvil.server.call('add_assessment', subject, details, start_date, due_date)
__assessments = None
my_assessment()
:class: notice
- **line 46** → creates the **add_assessment** method, which requires **subject**, **details**, **start_date** and **due_date** to be passed
- **line 47** → allows the method to edit the value of `__assessments`
- **line 49** → informs the developed that the database is being accessed
- **line 50** → writes the new assessment details to the database
- **line 51** → resets the `__assessment` value to `None`
- **line 52** → caches the updated assessment list
Now we need to replace the call to the database, with a call to our new method.
- Open AddComponent in Code mode
- Replace line 47 with the highlighted code below
:linenos:
:lineno-start: 31
:emphasize-lines: 17
def button_add_click(self, **event_args):
# validation
if not self.text_box_subject.text:
self.display_error("Subject name needed")
elif not self.text_box_details.text:
self.display_error("Assessment details needed")
elif not self.date_picker_start.date:
self.display_error("Start date needed")
elif not self.date_picker_due.date:
self.display_error("Due date needed")
else:
self.subject = self.text_box_subject.text
self.details = self.text_box_details.text
self.start = self.date_picker_start.date
self.due = self.date_picker_due.date
self.display_save(f"{self.subject} {self.details} assessment: {self.start} to {self.due} recorded")
data_access.add_assessment(self.subject, self.details, self.start, self.due)
self.reset_form()
:class: notice
- **line 47** → calls our new caching method and passes the required values
Lets check that this worked.
- Launch your website and navigate to the Add page.
- Add a new assessment
- Return to the Home page
- Check if your new assessment is shown
Good. But are there more places where our cache can cause problem? When else do we write data to the database? How about when we change assessment details?
- Launch your web app
- Edit the value of one of the assessments → good, it changes
- Navigate to the Add page
- Return to the Home → it's the previous value
Again this is the result of changing the database values without updating the cache. So lets fix that too.
Just like before, we will create a method in the data_access module that writes to the database and then reloads the cache.
- Open the data_access module
- At the bottom of the module add the highlighted code
:linenos:
:lineno-start: 54
:emphasize-lines: 1 - 14
def update_assessment(assessment_id, subject, details, start_date, due_date, completed):
global __assessments
print("Updating assessment details on the database")
anvil.server.call('update_assessment',
assessment_id,
subject,
details,
start_date,
due_date,
completed
)
__assessments = None
my_assessment()
:class: notice
- **line 54** → creates the update_assessment method with all the required data to be passed
- **line 55** → allows the method to change the `__assessments` value
- **line 57** → lets the developer know that the database is being accessed
- **line 58 - 65** → writes the changes to the database
- **line 66** → resets the `__assessments` value to `None`
- **line 67** → caches the assessments data
- Open the AssessmentPanel
- In the import section add the highlighted line
:linenos:
:lineno-start: 1
:emphasize-lines: 9
from ._anvil_designer import AssessmentPanelTemplate
from anvil import *
import anvil.server
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import datetime
from ... import data_access
:class: notice
- **line 9** → gives **HomeComponent** access to the **data_access** methods
- In the button_save_click handler replace line 37 - 44 with the highlighted code below
:linenos:
:lineno-start: 35
:emphasize-lines: 3-9
def button_save_click(self, **event_args):
# write to server
data_access.update_assessment(self.item.get_id(),
self.text_box_subject.text,
self.text_box_details.text,
self.date_picker_start.date,
self.date_picker_due.date,
self.check_box_completed.checked
)
# update display
self.label_subject.text = self.text_box_subject.text
self.label_details.text = self.text_box_details.text
self.label_start.text = self.date_picker_start.date.strftime('%d/%m/%Y')
self.label_due.text = self.date_picker_due.date.strftime('%d/%m/%Y')
self.switch_components()
:class: notice
- **line 37-43** → uses our new caching method to make changes to the assessment data
Lets check our latest iteration of code
- Launch you website
- Change an assessment's detail
- Navigate to another page
- Return to Home
- Check if your change is displayed.
It should all be working now.
By the end of this tutorial your code should be the same as below:
:linenos:
import anvil.server
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
# cached values
__user = None
__assessments = None
def the_user():
global __user
if __user:
print("Using cached user")
return __user
print("Accessing user from database")
__user = anvil.users.get_user()
return __user
def logout():
global __user
__user = None
anvil.users.logout()
def update_user(first_name, last_name):
global __user
print("Writing user details to database")
anvil.server.call('update_user', first_name, last_name)
__user = None
__user = the_user()
def my_assessment():
global __assessments
if __assessments:
print("Using cached assessments")
return __assessments
print("Accessing assessments from database")
__assessments = anvil.server.call('get_assessment')
return __assessments
def add_assessment(subject, details, start_date, due_date):
global __assessments
print("Writing assessment details to the database")
anvil.server.call('add_assessment', subject, details, start_date, due_date)
__assessments = None
my_assessment()
def update_assessment(assessment_id, subject, details, start_date, due_date, completed):
global __assessments
print("Updating assessment details on the database")
anvil.server.call('update_assessment',
assessment_id,
subject,
details,
start_date,
due_date,
completed
)
__assessments = None
my_assessment()
:linenos:
from ._anvil_designer import HomeComponentTemplate
from anvil import *
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.users
from .. import data_access
class HomeComponent(HomeComponentTemplate):
def __init__(self, **properties):
# Set Form properties and Data Bindings.
self.init_components(**properties)
# Any code you write here will run before the form opens.
self.repeating_panel_1.items = data_access.my_assessment()
:linenos:
from ._anvil_designer import AddComponentTemplate
from anvil import *
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.users
from .. import data_access
class AddComponent(AddComponentTemplate):
def __init__(self, **properties):
# Set Form properties and Data Bindings.
self.init_components(**properties)
self.subject = ""
self.details = ""
self.start = None
self.due = None
# Any code you write here will run before the form opens.
self.label_message.visible = False
if data_access.the_user():
self.card_details.visible = True
self.card_error.visible = False
self.button_add.visible = True
else:
self.card_details.visible = False
self.card_error.visible = True
self.button_add.visible = False
def button_add_click(self, **event_args):
# validation
if not self.text_box_subject.text:
self.display_error("Subject name needed")
elif not self.text_box_details.text:
self.display_error("Assessment details needed")
elif not self.date_picker_start.date:
self.display_error("Start date needed")
elif not self.date_picker_due.date:
self.display_error("Due date needed")
else:
self.subject = self.text_box_subject.text
self.details = self.text_box_details.text
self.start = self.date_picker_start.date
self.due = self.date_picker_due.date
self.display_save(f"{self.subject} {self.details} assessment: {self.start} to {self.due} recorded")
data_access.add_assessment(self.subject, self.details, self.start, self.due)
self.reset_form()
def display_error(self, message):
self.label_message.visible = True
self.label_message.foreground = "#ff0000"
self.label_message.icon = "fa:exclamation-triangle"
self.label_message.bold = True
self.label_message.text = message
def display_save(self, message):
self.label_message.visible = True
self.label_message.foreground = "#000000"
self.label_message.icon = "fa:save"
self.label_message.bold = False
self.label_message.text = message
def reset_form(self):
self.subject = ""
self.details = ""
self.start = None
self.due = None
self.text_box_subject.text = ""
self.text_box_details.text = ""
self.date_picker_start.date = None
self.date_picker_due.date = None
from ._anvil_designer import AssessmentPanelTemplate from anvil import * import anvil.server import anvil.users import anvil.tables as tables import anvil.tables.query as q from anvil.tables import app_tables import datetime from ... import data_access
:linenos:
class AssessmentPanel(AssessmentPanelTemplate):
def __init__(self, **properties):
# Set Form properties and Data Bindings.
self.init_components(**properties)
# Any code you write here will run before the form opens.
self.check_box_completed.checked = self.item['completed']
self.label_subject.text = self.item['subject']
self.label_details.text = self.item['details']
self.label_start.text = self.item['start_date'].strftime('%d/%m/%Y')
self.label_due.text = self.item['due_date'].strftime('%d/%m/%Y')
def check_box_completed_change(self, **event_args):
new_value = self.check_box_completed.checked
anvil.server.call('update_assessment_completed', self.item.get_id(),new_value)
def button_edit_click(self, **event_args):
self.text_box_subject.text = self.item["subject"]
self.text_box_details.text = self.item["details"]
self.date_picker_start.date = self.item["start_date"]
self.date_picker_due.date = self.item["due_date"]
self.switch_components()
def button_save_click(self, **event_args):
# write to server
data_access.update_assessment(self.item.get_id(),
self.text_box_subject.text,
self.text_box_details.text,
self.date_picker_start.date,
self.date_picker_due.date,
self.check_box_completed.checked
)
# update display
self.label_subject.text = self.text_box_subject.text
self.label_details.text = self.text_box_details.text
self.label_start.text = self.date_picker_start.date.strftime('%d/%m/%Y')
self.label_due.text = self.date_picker_due.date.strftime('%d/%m/%Y')
self.switch_components()
def switch_components(self):
# display elements
self.label_subject.visible = not self.label_subject.visible
self.label_details.visible = not self.label_details.visible
self.label_start.visible = not self.label_start.visible
self.label_due.visible = not self.label_due.visible
self.button_edit.visible = not self.button_edit.visible
# edit elements
self.text_box_subject.visible = not self.text_box_subject.visible
self.text_box_details.visible = not self.text_box_details.visible
self.date_picker_start.visible = not self.date_picker_start.visible
self.date_picker_due.visible = not self.date_picker_due.visible
self.button_save.visible = not self.button_save.visible