Logic
Introducing logic
Sometimes you want to answer a question with some logic rather than directly asking the user for information.
Docassemble uses Python, a powerful but easy to read programming
language to control logic inside an interview. Python statements go inside a
code
block.
---
code: |
# This is a comment. notice that we assign a value with =, and we test a value with ==
if user_name == "Scotty":
secret_message = "Beam Me Up, Scotty"
else:
secret_message = "No message to report."
---
question: |
Hello, ${user_name}
subquestion: |
${secret_message}
mandatory: True
---
question: |
What is your name?
fields:
- Name: user_name
Both YAML and Python are picky about indentation. If you run into an error, check to make sure each line is indented the same way as the example above.
In the example above, we introduced the use of the |
line continuation marker,
or vertical pipe. We always use this when the text that follows could go on
multiple lines, and to handle special characters (like accented letters). You
also always use it for a code
block.
We also introduced the subquestion
specifier. subquestion
is used for longer
explanation text on a screen. It uses a smaller font that is not bold.
Sometimes you don't want to use logic to create a new variable. You can use
simple Python code right inside a question
or subquestion
block, although the
syntax is very slightly different.
---
question: Hello, ${user_name}
subquestion: |
% if user_name == 'Spock':
Live long and prosper.
% endif
mandatory: True
---
question: What is your name?
fields:
- Name: user_name
Notice that inside a question
block, the line with the if
statement starts
with %
. We also need to use an endif
statement, instead of using indentation
to show the beginning and end of the if
statement.
%
symbol in MakoThe ``%` symbol has a special meaning in Mako. It lets you use Python syntax at the start and end of a block, usually to control conditional text. It is very handy when you have a large block of text that you want to show or hide, especially if the block has formatting or also contains variables.
In comparison, the ${ }
we've already been using lets us
put much shorter bits of Python code in the middle of a block of text.
Inside the text of a question or subquestion, use the %
symbol when the Python
code or conditional logic you are using is meant to control the next line or lines of content.
Python allows us to go a little further than using just if-then. We can introduce
multiple branches using elif
:
---
code: |
# This is a comment. notice that we assign a value with =, and we test a value with ==
if user_name == "Scotty":
secret_message = "Beam Me Up, Scotty"
elif user_name == "Bones":
secret_message = "Dammit Jim!"
else:
secret_message = "No message to report."
---
question: |
Hello, ${user_name}
subquestion: |
${secret_message}
mandatory: True
---
question: |
What is your name?
fields:
- Name: user_name
Note: once a condition is satisfied in an if-then block of logic, Python moves on and stops the evaluation process. What do you think the result of this would be if the user puts "Bones" as their name?
---
code: |
# This is a comment. notice that we assign a value with =, and we test a value with ==
if user_name == "Scotty":
secret_message = "Beam Me Up, Scotty"
elif user_name == "Bones":
secret_message = "Dammit Jim!"
elif user_name == "Bones":
secret_message = "Here is the antidote!"
else:
secret_message = "No message to report."
---
question: |
Hello, ${user_name}
subquestion: |
${secret_message}
mandatory: True
---
question: |
What is your name?
fields:
- Name: user_name
What happened
The "Dammit Jim!" version of the secret message gets displayed because it is the first version of the test that matched. One the Python interpreter hits the first matching line, it stops checking to see if any other test matches.
An advanced exploration
In the logic exercise above, notice that the comparison between the user name and the name we want to check for is case sensitive. That means if we check for the name "Scotty" and the user types "scotty" we won't get a match.
We can solve it by adding more checks, like this:
code: |
if user_name == "Scotty" or user_name == "scotty":
secret_message = "Beam Me Up, Scotty"
But what if we want to match "sCotty" and "SCotty", in case the user accidentally holds the shift key at the wrong time? The one-by-one matching approach gets out of hand pretty quickly.
Note that what we explore below is an advanced concept. You can get pretty far with the building blocks we already explored. The advantage of Docassemble is that we can use these advanced solutions. So let's see what that might look like.
A universal approach
When faced with this problem, someone with a computer science mindset will think about how to make all of the different possible capitalizations of the names equivalent to each other.
Luckily, there is a built-in way to do this in Python. It involves a few new concepts:
- We can transform the value of variables in Python by using
functions
andmethods
. - Python has a lot of built-in functions and methods. We usually don't have to create a new one for basic tasks.
- We can transform a copy of a value without destroying the original.
- We use
functions
by wrapping them around a variable, likefunction_name(variable_name)
. Methods are similar, but we use them by adding a.
to the end of the variable name, instead, likevariable.method_name()
. The reason for the difference isn't critical right now.
Pause for a minute and reflect on this question:
What is the "universal" version of the name "Scotty"?
You likely came up with one of three answers:
- Scotty
- scotty
- SCOTTY
Suppose we change the user_name
variable's capitalization to one of these three forms, regardless of the
original capitalization of the variable. Then we can compare it to the exact string we know it should
match, "Scotty", "scotty" or "SCOTTY".
How do we change the capitalization of the user_name
variable?
Python has handy built-in methods
that let us transform text. Here are two likely
candidates:
We can eliminate the idea of comparing it to the version with an initial capital letter ("Scotty"). While we could achieve it, notice that it would just add an extra step. We'd need to make everything lowercase first before changing the capitalization of the first letter.
Here's how we use the .lower()
and .upper()
methods:
user_name.lower()
applies the .lower()
method to the text and returns
a lowercase
version of the text. It doesn't change the value that is stored in user_name
permanently.
We can change our comparison to use this method
, like this:
code: |
if user_name.lower() == "scotty":
secret_message = "Beam Me Up, Scotty"
We would follow the same pattern to use the .upper()
method:
code: |
if user_name.upper() == "SCOTTY":
secret_message = "Beam Me Up, Scotty"
Most programmers will choose the lowercase method first. But it's just habit. Either method would work fine.
Suppose we really wanted to try comparing to "Scotty", despite the extra steps it requires.
We can do that by chaining the .lower()
method with the .capitalize()
method:
code: |
if user_name.lower().capitalize() == "Scotty":
secret_message = "Beam Me Up, Scotty"
What to take away from this advanced example
- Logic in Docassemble can take advantage of the full Python programming language
- Python is full of many helpful building blocks. You can look outside of the Docassemble documentation to find them.
We've introduced a lot of new concepts here. But you will be able to do a lot of the Python code in your Docassemble interview by copying and pasting and making small changes to other working examples. Don't worry about understanding the full power of the Python language or memorizing the whole library of available functions and methods.
Your assignment
- Modify the Logic exercise so that a new secret message is displayed when a name of your choice is displayed.
- Make all of the secret messages work regardless of how the user
capitalizes their name. Apply the example that we used to check for upper and lowercase
versions of
scotty
to the other conditions.
Quinten Steenhuis, June 2020