A common computer science concept, the Class, is a key benefit of Docassemble over other document assembly platforms. But when I work with a new Docassemble learner, or evangelize Docassemble to users of other platforms, Classes and Objects are the concept most likely to draw a blank look or create confusion. This post aims to demystify classes and object-oriented programming, with just enough detail to keep it relevant to you lawyer-programmers. I won’t try to explain the built-in Docassemble objects, because the documentation already does that quite well.

You don’t need to be a computer scientist to use Classes. By the end of this post, you’ll know what a Class is, and hopefully you’ll consider using one or more in your next document assembly project. Many folks have explained what object-oriented programming is all about. I’ll focus my explanation on the features that are useful for a document assembly platform like Docassemble.

Introducing datatypes and variables

Before explaining Classes, a quick review of both variables and datatypes. If you’re building interviews in Docassemble, you probably already know the basics here.

A variable or field (synonyms in Docassemble) is a bucket that can hold one piece of information. Think of it as a single cell in a spreadsheet, or a single blank field on a form, such as the client’s name. The datatype of that variable tells us what type of information is stored.

Docassemble, which uses Python, has a few built-in primitive datatypes:

  • Text, which is made up of one or more letters, symbols, or numerals (text data is called a string in Python and most other computer programming languages, str for short)
  • Numbers, which can be divided into:
    • Integers or whole numbers (int for short)
    • Real numbers, which can contain a decimal point followed by one or more numbers (called float or floating point numbers in Python and other programming languages)
  • True/False (called bool or Boolean values)
  • Groups which represent a collection of one or more other variables, which can be divided into:
    • Lists (like Arrays in other languages, a basic, ordered, group of items. You can retrieve via a numeric index)
    • Tuples (like lists, but with the special feature that they are ordered and unchangeable)
    • Sets (sets are unordered, and an item can appear in a set only once)
    • Dictionaries (like lists, but you can retrieve an item in a dictionary using a lookup value called a key)

Although I think it makes sense to introduce groups here, internally, all of the four basic group types are represented as Objects in Python.

Why care about datatypes? Well, it doesn’t matter if we just spit the information back out completely unchanged into a template. This is the basic mail merge-style document automation that actually can solve a lot of problems. But pretty often, we want to do some kind of operation on that information. And these operations depend on knowing what type of data we are collecting. For example, we may gather every source of income that the user has and then add it together to arrive at a total. It doesn’t make sense to total up a text field. Or very frequently, we want to gather a True or False value, and use an if then… statement to ask a follow-up question or control the display of information in the template. There are also special operations we can do on strings, such as translating, capitalizing, or joining together, that don’t work on other types of data.

A basic mail merge doesn’t require knowing what datatype each cell contains.

Behind the scenes, these datatypes are the basic building blocks of every kind of data you collect in a Docassemble interview, including more complex kinds of data like dates and currency amounts.  It’s handy that each type of data can fit it in its own category. What if you want to categorize something more complicated in the world? When you want to represent a more complex type of data, you turn to a Class.

Learn more detailed information about Python datatypes.

What is a Class? What is an Object?

A Class is a handy way to describe something that exists in the world. It is a collection of features or attributes, and can also specify methods or functions that describe how to interact with that “something”. You can think of a Class as a recipe, say, for bread. An Object would be the loaf of bread you made following that recipe. It is the term for a specific instance of a class with its attributes filled in.

Another way to think of a Class is as a form, much like the one you may want to automate. It has a list of specific kinds of information you want to collect. An Object then, could be thought of as an entry in a spreadsheet or database where you store the information you collected on that form, representing one copy of that form which has been filled out.

Let’s use a legal example. If you wanted to describe a specific court, what attributes might be useful to know? Here are a few:

  • Name
  • Type of court
  • Location
  • Mailing address
  • Jurisdiction

Here’s a standardized UML representation of what our Court class might look like, with datatypes and methods included:

Two things to pay special attention to here: location and mailing_address are both of type Address, which is a built-in Class in Docassemble. It handles storing zip code, street name, etc. in a standardized and internationalized way. An object can store other objects. Second, notice we have a method named has_jurisdiction. When we run this method, it will return True or False. It takes an Address object as its argument.

Remember, an Object is an instance of a Class, with the attributes filled-in to have specific values. The object gets its own unique name, but the attributes and methods use the standardized names set in the Class’s definition. Here’s what an object that uses this class might look like:

When you want to refer to an object in your Docassemble code, the Object itself gets its own variable name. Then the attributes of the object are retrieved using dot notation. For our example object tenant_court, if we want to display the name of the court in a Docx template file, we would write {{ tenant_court.name }}.

Suppose we gathered the client’s address into an Address object, named tenant_address. What if we want to know if the client’s address is inside the jurisdiction of tenant_court? We could write something that looks like this:

if tenant_court.has_jurisdiction(tenant_address):
  has_jurisdiction = True

Why bother with Classes and Objects?

The benefits of using Classes and Objects in your Docassemble interview really come into play most clearly for larger interviews. For me, the very practical benefits come from simplifying variable names, keeping track of and grouping related fields, and using abstraction to my advantage both to keep code out of my template file and allow me to “forget” the details of how I’ve implemented a feature.

Simplify variable names

Compare the variables needed to keep track of basic information about two parties in a HotDocs interview vs a Docassemble interview, using naming conventions taken from actual interviews I’ve worked on:

HotDocsDocassemble
Plaintiff name TE
Plaintiff street address TE
Plaintiff zip TE
Plaintiff city TE
Plaintiff state TE
Plaintiff phone TE

Defendant name TE
Defendant street address TE
Defendant zip TE
Defendant city TE
Defendant state TE
Defendant phone TE
plaintiff: Individual
defendant: Individual

All of the different attributes of a party are logically grouped together in a Class, in a standardized way that only has to be defined once. To describe my Plaintiff and Defendant, instead of defining 12 separate variables, I can just define two, and in the background Docassemble creates an object with all of the attributes needed.

Encapsulation and Abstraction

Classes also give you the benefit of providing an abstract interface into your object’s attributes without needing to know about implementation details. You can write a method once that does some operation on the attributes, and reuse it for every instance of the class without having to copy and paste code or risk an error. Think about a toaster. You don’t need to know exactly how the toaster works to make toast. You just put the bread in and push the button to start it. Like a toaster’s button, an Object can give you abstract ways to interact with it so you don’t have to reinvent the wheel.

To use a toaster, you don’t need to know how it heats the bread. Just load in the bread and press the lever.

Let’s look at an example for an actual class I have created for a Docassemble interview.

First, I defined a DiscoveryRequest class that represents a single request. It can be checked or unchecked, and has a category and a description. I display the list of requests to the person who is using my interview. They can choose which requests that they want to select for the final pleading.

Second, I defined a dictionary (remember, a dictionary is one type of group or collection of data you can use in Python) which represents one or more requests.

Suppose that my DiscoveryDict is storing a list of Interrogatories. I want to make sure that I never have more than 30 interrogatories selected, because under the Massachusetts Rules of Civil Procedure, that is the most interrogatories that can be asked without leave of court.

To help me with enforcing that rule, I defined a method that is part of the DiscoveryDict class to count the number of checked items in my dictionary. I can’t simply use the built-in function to count how many items are in my dictionary, because it contains all of the available discovery requests, not just the checked ones.

I can use my new method like this:

if my_requests.count_checked() < 30:
  do_something = True

Notice that when I use my new method, I don’t need to know anything at all about how the information is stored in my special dictionary. This makes my code easier to update if I ever jump into it later, easier to share with other parties, easier to read, and more robust. Similarly, if I want to display a category heading in my formatted list of discovery object, I can control it in my template file with the any_in_category method:

{% if interrogatories.any_in_category('Bad Housing Conditions'): %}
Bad Housing Conditions
{% endif %}

Inheritance

Let’s return to the Discovery example. For now, we may not really care what type of DiscoveryRequest we have. But suppose we want to add special attributes to Admissions, Document Requests, and Interrogatories. Would we have to copy and paste the attributes we’ve already defined for a basic Discovery Request for each special type?

No. One special feature of Classes is that one Class can inherit methods and attributes from a parent class, allowing you to re-use the code from the parent in your more specific class. Here’s what that looks like for our DiscoveryRequest example:

Each child class inherits the same attributes and methods of the parent class. Because the objects inherit from the same parent class, all of the methods of the DiscoveryDict class will work on these new objects too. We can be specific about the type of Request and make methods that do different things to the different types, all while benefiting from the existing code we wrote that uses the features of the basic type.

Conclusion

Classes can be the most abstract concept you run into in Docassemble. However, understanding how they work can unlock a powerful new way for you to write your Docassemble interview that saves you time and improves the quality and robustness of your final product. Let me know below if you have anything you’d want to add or if you find the information helpful!


3 Comments

Melanie · September 14, 2018 at 7:39 pm

Hi, Lawyer learning python here. Are the attributes of the classes built into docassemble? In the address example it seems like you’re saying Address already has the attributes for zip code, street name, etc. How many different docassemble classes are there that already have attributes? If they don’t all have attributes, how do you give them attributes? Thanks very much for the great article…

    Quinten Steenhuis · November 23, 2018 at 11:15 am

    Melanie, so sorry for the long delay.

    This article deserves a follow-up. Python uses “lazy” classes (you don’t need to specify each attribute in advance), and Docassemble relies on that to prompt the user with a question that provides certain information. If you define the attribute with a default value in the class definition, it can cause problems with Docassemble’s gathering of the information, so you usually leave it blank. What normally prompts the use of the fields is either referencing them directly, such as referring to client.address.address in a template, or by using a method that needs those attributes to be defined, such as client.address.full(). There’s no special way to add an attribute into an object in Python or Docassemble: just directly assign it. E.g., Individual.my_made_up_attribute = 123 will work. The list of pre-defined attributes is generally documented pretty well on the Docassemble documentation pages.

Peter Ginakes · November 6, 2018 at 8:56 pm

Can you integrate Docassemble with the databases in Time Matters to create templates for WordPerfect?

Leave a Reply

Your email address will not be published. Required fields are marked *