docassemble allows interview developers to use Python, a general purpose programming language, to control the direction of interviews and do things with user input. It is not necessary to use Python code when developing an interview, but it is an extremely powerful tool.
Python appears in docassemble interviews in a number of ways:
- Every variable name in an interview is a Python variable,
whether developers realize it or not. The value of the variable
might be text (e.g.,
"123 Main Street"
), a number (e.g.,42
), a special value that has meaning in Python (e.g.True
,False
, andNone
), a group (e.g., a list, dictionary, or set), an object, or an attribute of an object. - Developers can use
code
blocks to set variables using Python code, which may act upon user input. - Python code can be embedded within
question
s, for example to generate a list of choices in a multiple-choice list. - The Mako templating system, which developers can use to format
questions and documents, is based on Python, and allows
developers to embed Python statements within templates. There are
slight syntax differences between Mako and Python. For example,
Mako requires that if/then/else logic statements be closed with an
endif
statement.
An introduction to coding in Python
As general purpose programming languages go, Python is relatively user-friendly and readable. Python programmers don’t need to worry that their code will fail because of a missing semicolon.
Simple examples: arithmetic
Here is some very simple Python code:
This code sets the variable answer
to 2 + 2. The code is contained
in a code
block, which is explained below.
Here is a more complicated example:
This code first sets the variable a
to the number 2. Then it sets
the variable b
to the number 3. Then it sets the variable answer
to the sum of a
and b
, which is 5.
Note that once a variable is set, its value does not change. In the
code below, the answer
is still 5, even though b
is changed to
1
.
The code
blocks can contain multiple lines of code, which are
processed one at a time.
It is also possible to run Python code in a more limited way within a
Mako template, using Mako’s ${}
syntax.
The contents of ${ ... }
are processed as Python code. The code
that can be placed inside ${ ... }
is limited to one line of code,
the result of which is then placed into the text of the question. So
you could not include multiple lines of code within a ${ ... }
expression.
You can do complicated arithmetic with Python:
Note that the spaces within this code are purely aesthetic; the code will still function without them:
However, using spaces in your code is highly recommended, because they make the code much more readable!
Conditional actions: if/then/else statements
Sometimes you want different code to run differently depending on certain conditions. In computer programming, the simplest form of a “conditional statement” is the if/then/else statement, where you tell the computer that if a certain condition is true, then do something, or do something else if the condition is false.
For example:
Here, the condition to be evaluated is b > a
. The >
symbol means
“greater than.” (The <
symbol means “less than.”) Since b
is 5
and a
is 4, and 5 is greater than 4, the condition is true.
Therefore, the lines b = 62
and answer = 20
will be run, and the
code answer = 40
will be ignored.
There are several important things to note in this example because they illustrate the syntax of the Python language:
- The
if
andelse
statements end in a colon:
, after which the line ends.- Rule: This colon must be there. If you forget the colon, you will get an “invalid syntax” error.
- The lines after the
if
andelse
lines are indented. The indentation indicates which lines are referred to by the colon, and which are not.- Rule: There must be at least one indented line following the colon. If you don’t have an indented line following a colon, you will see the error “IndentationError: expected an indented block.”
- At the end,
b
will be set to 0. Although the line followselse:
, it is not indented relative to theelse:
line. - The lines
b = 62
andanswer = 20
are both indented by two spaces.- Rule: While the number of spaces is not important (1, 2, 3, 4, or more spaces would all be valid) the indentation following the colon must be consistent. If you use inconsistent indentation, you will see the error “IndentationError: unindent does not match any outer indentation level.”
These are important rules in Python. In other programming
languages, line breaks and spaces do not matter, and punctuation marks
like {
, }
, and ;
are used to separate different pieces of code.
In Python, however, line breaks and spaces are important; they serve
the same purposes that {
, }
, and ;
serve in other languages.
You can have multiple layers of indentation. For example:
In addition to greater than (>
) and less than (<
), the following
conditional operators are available in Python:
a == b
is true ifa
equalsb
. There are two equal signs to distinguish this froma = b
, which sets the value ofa
to the value ofb
. This works with numbers as well as with text.a is b
is essentially synonomous witha == b
.a >= b
is true ifa
is greater than or equal tob
.a <= b
is true ifa
is less than or equal tob
.a in b
is true ifb
is a list, dictionary, or set, anda
is contained withinb
. For example,42 in [13, 42, 62]
is true. This also works with text. If you doa = "Fred"
, thena in ["Mary", "Fred", "Scott"]
will be true, whilea in ["Harold", "Anthony", "Norman"]
will be false. In the case whereb
is a dictionary,a in b
will return true ifa
is a key withinb
.
The following conditions apply when the variables are text.
a.rfind(b) >= 0
will return true ifa
andb
are both text strings andb
is contained withina
.a.startswith(b)
returns true if the text inb
is the start of the text ina
.a.endswith(b)
returns true if the text inb
is at the tail end of the text ina
.
Going through the items in a group
There are special types of variables in Python that help you manage collections of things. For more information about this, see the groups section.
The code
block
In a docassemble interview, a question
block tells
docassemble that if the interview logic wants to know the value of
a particular variable, such as best_fruit_ever
, and that variable
has not been defined yet, docassemble can pose the question to the
user and the user’s answer to the question may provide a definition
for that variable.
For example:
This question
asks the user to type in the name of the best fruit
ever.
The value of variables like best_fruit_ever
can also be retrieved by
running Python code contained within code
blocks:
This code
“question” is “asked” in much the same way that the
previous question
question is asked: if and when it needs to be
asked. docassemble “asks” code
questions not by asking for the
user’s input and then processing the user’s input, but by running the
Python code contained in the code
statement.
As with user question
s, docassemble might find that “asking” the
code
question did not actually define the needed variable. In that
case, it goes looking for another question (which could be of the
question
or code
variety) that will provide a definition.
Once best_fruit_ever
is defined, docassemble will not need to
run the code
again if the interview logic calls for
best_fruit_ever
at a later point. In the same way, docassemble
does not need to ask the user for the user’s name every time it needs
to know the user’s name.
The code
can do anything Python can do, such as retrieve information
from the web:
or pick a random value from a list:
(If you don’t remember what an imports
block does, see
initial blocks.)
All of the variables you set with question
blocks are available to
your Python code. If your code uses a variable that is not defined
yet, docassemble will “ask” question
blocks and code
blocks in
order to define the variables.
Consider the following example:
If docassemble needs to know product_recommendation
, it will
execute the code block, but the code block will fail to execute
because user_age
is undefined. docassemble will then go looking
for a question that answers user_age
, and it will ask the user “What
is your age?” Upon receiving a response, docassemble will
continue in its effort to find a definition for
product_recommendation
and will complete the execution of the code
block.
code
block modifiers
You can change the way code
blocks work by adding modifiers:
reconsider
: Ifreconsider
is set toTrue
, then docassemble will always “reconsider” the values of any of the variables set by thecode
block. That is, every time the interview logic is evaluated (every time the screen loads) docassemble will forget about the value of any of the variables set by thecode
block.initial
: Ifinitial
is set toTrue
, then docassemble will run the code every time the interview logic is evaluated (every time the screen loads).mandatory
: Ifmandatory
is set toTrue
, then docassemble will run the code when the interview logic is evaluated, except that once the code runs through all the way, docassemble will remember that thecode
block was successfully run, and it will not re-run it again, as it does withinitial
code.
For more information about these modifiers and how they are used, see the Interview Logic section.
Limitations
You can run any Python code within code
blocks, but there are
some constraints based on the way docassemble works:
- After each screen loads, the variables are serialized with pickle.
Any name in the global namespace that refers to something
non-pickleable will be omitted from this serialization. So, you can
define a function
foo()
with some code, but when the next screen loads, the namefoo
will be undefined (as thoughreconsider
is set toTrue
). Thus, docassemble will need to seek out the definition offoo
, and will re-run thecode
block that defines the functionfoo
. - You can include a
class
definition incode
, but any instances of objects of that class will not be serializable, and an exception will be raised. So if you want to use custom classes, write a module, usemodules
to import all the names from the module, and useobjects
to instantiate objects of your custom class. - You can use the standard Python statements
import
andfrom ... import
to import names, but if the names you are importing refer to classes of objects that you will create and expect to be serialized, put yourimport
andfrom ... import
statements ininitial
code. Otherwise, the serialization process may raise an exception. Better yet, stick with usingmodules
andimports
to bring in names from other packages, and then you don’t have to worry about this. - When docassemble prepares the variables for serialization, it will discard non-serializable names in the global namespace, but it does not do this recursively. So you can feel free to use non-serializable types in the global namespace, but if you use non-serializable types within lists, dictionaries, or attributes, an exception will be raised.
While docassemble will allow you to do many things with code in
code
blocks, it is a best practice to put complicated code into
modules, and only use rudimentary Python code in your interviews.
Ideally, non-programmers should at least be able to read and edit
interview files, because subject matter experts are often not adept at
coding. The more Python code you put into an interview file, the
more non-programmers will be intimidated by the interview file and be
unwilling to work with it. If you can hide complexity behind a simple
functional interface, you should do so.