diff --git a/docassemble/AssemblyLine/al_courts.py b/docassemble/AssemblyLine/al_courts.py
index daaf1d4b..3235d99c 100644
--- a/docassemble/AssemblyLine/al_courts.py
+++ b/docassemble/AssemblyLine/al_courts.py
@@ -22,20 +22,51 @@
class ALCourt(Court):
- """Object representing a court in Massachusetts. We use a function on the CourtList object that filters courts by
+ """
+ Object representing a court in Massachusetts. We use a function on the CourtList object that filters courts by
address and can use any of those three features of the court to do the filtering.
+
+ Example:
+ In an interview:
+
+ ```yaml
+ objects:
+ - trial_court: ALCourt
+ ---
+ code: |
+ trial_court.name = "Example District Court"
+ trial_court.address.address = "123 Main Street"
+ trial_court.address.city = "Boston"
+ trial_court.address.state = "MA"
+ trial_court.address.zip = "02108"
+ ---
+ question: |
+ Your court
+ subquestion: |
+ ${ trial_court.short_label_and_address() }
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Create a new court object.
+ """
+ Create a new court object.
Args:
*pargs: Standard DAObject positional arguments.
**kwargs: Standard DAObject keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - trial_court: ALCourt
+ ```
"""
super().init(*pargs, **kwargs)
if "address" not in kwargs:
@@ -84,6 +115,29 @@ def short_label(self) -> str:
Returns:
str: string representing the court's name, with city if needed to disambiguate.
+
+ Example:
+ With `trial_court.name = "District Court"` and
+ `trial_court.address.city = "Boston"`, the city is added because it is
+ not already part of the name:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ trial_court.short_label() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ trial_court.short_label() }}
+ ```
+
+ **Output**
+
+ ```text
+ District Court (Boston)
+ ```
"""
# Avoid forcing the interview to define the court's address
if hasattr(self, "address") and hasattr(self.address, "city"):
@@ -100,6 +154,19 @@ def short_label_and_address(self) -> str:
Returns:
str: string representing the court's name and address.
+
+ Example:
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ trial_court.short_label_and_address() }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ trial_court.short_label_and_address() }}
+ ```
"""
return f"**{ self.short_label() }**[BR]{ self.address.on_one_line() }"
@@ -111,6 +178,19 @@ def short_description(self) -> str:
Returns:
str: string representing the court's name and description.
+
+ Example:
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ trial_court.short_description() }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ trial_court.short_description() }}
+ ```
"""
all_info = f"**{ self.short_label() }**"
if hasattr(self, "address"):
@@ -129,6 +209,14 @@ def from_row(
Args:
df_row: Pandas Series object.
ensure_lat_long: bool, whether to use Google Maps to geocode the address if we don't have coordinates.
+
+ Example:
+ Given a pandas DataFrame loaded from your court data:
+
+ ```yaml
+ code: |
+ trial_court.from_row(court_dataframe.iloc[0])
+ ```
"""
# A few columns we expect to see:
# name
@@ -193,12 +281,28 @@ def geolocate(self) -> None:
Use Google Maps to geocode the court's address and store the result in the location attribute.
Deprecated: use geocode() instead.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ trial_court.geolocate()
+ ```
"""
self.geocode()
def geocode(self) -> None:
"""
Use Google Maps to geocode the court's address and store the result in the location attribute.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ trial_court.geocode()
+ ```
"""
self.address.geocode()
self.location = self.address.location
@@ -213,14 +317,41 @@ class ALCourtLoader(DAObject):
Attributes:
filename (str): Path to the file containing court information.
converters (Dict[str, Callable]): A dictionary of functions to apply to columns in the dataframe.
+
+ Example:
+ Place your court spreadsheet in the package’s `data/sources` directory:
+
+ ```yaml
+ objects:
+ - all_courts: ALCourtLoader.using(filename="courts.xlsx")
+ ---
+ question: |
+ Which court is handling your case?
+ fields:
+ - Court: selected_court_index
+ code: all_courts.all_courts()
+ ---
+ code: |
+ trial_court = all_courts.as_court("trial_court", selected_court_index)
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Create a new courtloader object.
+ """
+ Create a new courtloader object.
Args:
*pargs: Standard DAObject positional arguments.
**kwargs: Standard DAObject keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - all_courts: ALCourtLoader.using(filename="courts.xlsx")
+ ```
"""
super().init(*pargs, **kwargs)
self.package = docassemble.base.functions.this_thread.current_question.package
@@ -242,6 +373,17 @@ def all_courts(self) -> List[Tuple[int, str]]:
Returns:
List[Tuple[int, str]]: List of tuples where each tuple contains (dataframe_index, display_value). The dataframe_index (int) can be used with as_court() to retrieve the full court object. The display_value (str) is the court's name or other display column value.
+
+ Example:
+ With `all_courts` configured as an ALCourtLoader for your spreadsheet:
+
+ ```yaml
+ question: |
+ Which court is handling your case?
+ fields:
+ - Court: selected_court_index
+ code: all_courts.all_courts()
+ ```
"""
return self.filter_courts(None)
@@ -256,6 +398,14 @@ def unique_column_values(self, column_name: str) -> Set[str]:
Set[str]:.
- A set containing unique values from the specified column.
- Returns an empty set if the column does not exist or an error occurs.
+
+ Example:
+ With `all_courts` configured as an ALCourtLoader for your spreadsheet:
+
+ ```yaml
+ code: |
+ court_departments = all_courts.unique_column_values("department")
+ ```
"""
df = self._load_courts()
try:
@@ -273,6 +423,14 @@ def county_list(self, column_name: str = "address_county") -> Set[str]:
Returns:
Set[str]: A list of all unique values in the specified row in the given spreadsheet.
+
+ Example:
+ With `all_courts` configured as an ALCourtLoader for your spreadsheet:
+
+ ```yaml
+ code: |
+ court_counties = all_courts.county_list()
+ ```
"""
return self.unique_column_values(column_name)
@@ -290,6 +448,14 @@ def county_has_one_court(
Returns:
bool: True if there is only one court associated with the specified county in the spreadsheet.
+
+ Example:
+ With `all_courts` configured as an ALCourtLoader for your spreadsheet:
+
+ ```yaml
+ code: |
+ has_one_court_in_county = all_courts.county_has_one_court(users[0].address.county)
+ ```
"""
return (
len(self.filter_courts(court_types=county_name, column=county_column)) == 1
@@ -314,6 +480,13 @@ def county_court(
Returns:
ALCourt: The first court matching the county name.
+ Example:
+ With `all_courts` configured as an ALCourtLoader for your spreadsheet:
+
+ ```yaml
+ code: |
+ trial_court = all_courts.county_court("trial_court", users[0].address.county)
+ ```
"""
matches = self.filter_courts(court_types=county_name, column=county_column)
if len(matches) > 0:
@@ -346,6 +519,17 @@ def matching_courts_in_county(
Returns:
List[Tuple[int, str]]: List of tuples where each tuple contains (dataframe_index, display_value). The dataframe_index (int) can be used with as_court() to retrieve the full court object. The display_value (str) is the court's name or other display column value.
+
+ Example:
+ With `all_courts` configured as an ALCourtLoader for your spreadsheet:
+
+ ```yaml
+ question: |
+ Which court is handling your case?
+ fields:
+ - Court: selected_court_index
+ code: all_courts.matching_courts_in_county(users[0].address.county)
+ ```
"""
return self.filter_courts(
court_types=county_name,
@@ -380,6 +564,17 @@ def filter_courts(
Returns:
List[Tuple[int, str]]: List of tuples where each tuple contains (dataframe_index, display_value). The dataframe_index (int) can be used with as_court() to retrieve the full court object. The display_value (str) is the court's name or other display column value.
+
+ Example:
+ With `all_courts` configured as an ALCourtLoader for your spreadsheet:
+
+ ```yaml
+ question: |
+ Which court is handling your case?
+ fields:
+ - Court: selected_court_index
+ code: all_courts.filter_courts("District")
+ ```
"""
df = self._load_courts()
if court_types:
@@ -413,6 +608,14 @@ def as_court(
Returns:
ALCourt: An ALCourt object initialized with data from the specified index.
+
+ Example:
+ With `all_courts` configured as an ALCourtLoader for your spreadsheet:
+
+ ```yaml
+ code: |
+ trial_court = all_courts.as_court("trial_court", selected_court_index)
+ ```
"""
court = ALCourt(intrinsicName)
df = self._load_courts()
diff --git a/docassemble/AssemblyLine/al_document.py b/docassemble/AssemblyLine/al_document.py
index e1f9e5f5..5b3f9211 100644
--- a/docassemble/AssemblyLine/al_document.py
+++ b/docassemble/AssemblyLine/al_document.py
@@ -78,7 +78,8 @@ class CacheableDocument(_CacheableDocumentTitle, total=False):
def random_suffix(length: int = 8) -> str:
- """Return a random string for use in unique IDs.
+ """
+ Return a random string for use in unique IDs.
Note: this is powerful enough for the expected usecase of distinguishing a few
HTML elements from each other, but not cryptographically secure or as strong as
@@ -88,6 +89,16 @@ def random_suffix(length: int = 8) -> str:
length (int): The length of the random string to generate. Defaults to 8.
Returns:
str: A random string of lowercase letters and digits.
+
+ Example:
+ Import `random_suffix` explicitly from
+ `docassemble.AssemblyLine.al_document` before using this example.
+ In an interview code block:
+
+ ```yaml
+ code: |
+ download_id = random_suffix(length=8)
+ ```
"""
alphabet = string.ascii_lowercase + string.digits
return "".join(secrets.choice(alphabet) for _ in range(length))
@@ -102,6 +113,16 @@ def base_name(filename: str) -> str:
Returns:
str: The base name of the file without its extension.
+
+ Example:
+ Import `base_name` explicitly from
+ `docassemble.AssemblyLine.al_document` before using this example.
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_name = base_name("petition.pdf")
+ ```
"""
return os.path.splitext(filename)[0]
@@ -118,6 +139,14 @@ def label(dictionary: dict) -> str:
Returns:
str: The value of the first dictionary item or an empty string if not found.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ column_heading = label({"name": "Full name"})
+ ```
"""
try:
return next(iter(dictionary.values()), "")
@@ -137,6 +166,14 @@ def key(dictionary: dict) -> str:
Returns:
str: The key of the first dictionary item or an empty string if not found.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ column_attribute = key({"name": "Full name"})
+ ```
"""
try:
return next(iter(dictionary.keys()), "")
@@ -157,6 +194,21 @@ def safeattr(object: Any, key: str) -> str:
Note:
The `location` attribute of an Address object or any LatitudeLongitude attribute of a DAObject is always skipped.
+
+ Example:
+ Show an email address only if it is already defined:
+
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ safeattr(users[0], "email") }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ safeattr(users[0], "email") }}
+ ```
"""
try:
if isinstance(object, dict) or isinstance(object, DADict):
@@ -186,6 +238,16 @@ def html_safe_str(the_string: str) -> str:
Returns:
str: A string that's safe for use as an HTML class or ID.
+
+ Example:
+ Import `html_safe_str` explicitly from
+ `docassemble.AssemblyLine.al_document` before using this example.
+ In an interview code block:
+
+ ```yaml
+ code: |
+ html_id = html_safe_str("Your documents")
+ ```
"""
return re.sub(r"[^A-Za-z0-9]+", "_", the_string)
@@ -208,6 +270,15 @@ def table_row(title: str, button_htmls: List[str] = []) -> str:
Returns:
str: An HTML string representing a row in an AL document-styled table.
+
+ Example:
+ Import `table_row` explicitly from
+ `docassemble.AssemblyLine.al_document` before using this example.
+ Add a row with a download button in question text (Mako):
+
+ ```mako
+ ${ table_row("Petition", [action_button_html(petition.as_pdf().url_for(attachment=True), label="Download", icon="download")]) }
+ ```
"""
html = (
f'\n\t
'
@@ -235,6 +306,16 @@ def pdf_page_parity(pdf_path: str) -> Literal["even", "odd"]:
Returns:
Literal["even", "odd"]: The parity of the number of pages in the PDF.
+
+ Example:
+ Import `pdf_page_parity` explicitly from
+ `docassemble.AssemblyLine.al_document` before using this example.
+ In an interview code block:
+
+ ```yaml
+ code: |
+ page_parity = pdf_page_parity(petition.as_pdf().path())
+ ```
"""
with pikepdf.open(pdf_path) as pdf:
num_pages = len(pdf.pages)
@@ -249,6 +330,17 @@ def add_blank_page(pdf_path: str) -> None:
Args:
pdf_path (str): Path to the PDF in the filesystem.
+
+ Example:
+ Import `add_blank_page` explicitly from
+ `docassemble.AssemblyLine.al_document` before using this example.
+ In an interview code block:
+
+ ```yaml
+ code: |
+ combined_pdf = al_user_bundle.as_pdf()
+ add_blank_page(combined_pdf.path())
+ ```
"""
# Load the PDF
with pikepdf.open(pdf_path, allow_overwriting_input=True) as pdf:
@@ -288,14 +380,35 @@ class ALAddendumField(DAObject):
Note:
The attributes `headers` and `field_style` are planned for future releases and are not currently implemented.
+
+ Example:
+ On an ALDocument named `petition` with `has_addendum=True`, configure
+ the interview variable `reasons` to overflow after 640 characters:
+
+ ```yaml
+ code: |
+ petition.overflow_fields["reasons"].overflow_trigger = 640
+ petition.overflow_fields["reasons"].label = "Reasons for the request"
+ petition.overflow_fields.gathered = True
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ code: |
+ petition.overflow_fields["reasons"].overflow_trigger = 640
+ ```
"""
super().init(*pargs, **kwargs)
@@ -329,6 +442,29 @@ def overflow_value(
Returns:
Any: The portion of the variable exceeding the content safe for display, considered as overflow.
+
+ Example:
+ With `reasons = "I need more time to move."` and
+ `petition.overflow_fields["reasons"].overflow_trigger = 16`:
+ Calling the field directly uses an empty overflow marker by default.
+
+ **Input (Mako)**
+
+ ```mako
+ ${ petition.overflow_fields["reasons"].overflow_value() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ petition.overflow_fields["reasons"].overflow_value() }}
+ ```
+
+ **Output**
+
+ ```text
+ to move.
+ ```
"""
# Handle a Boolean overflow first
if isinstance(self.overflow_trigger, bool):
@@ -389,6 +525,14 @@ def max_lines(self, input_width: int = 80) -> int:
Returns:
int: The maximum number of lines accommodated by the input width.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ lines_that_fit = petition.overflow_fields["reasons"].max_lines()
+ ```
"""
return floor(self.overflow_trigger / input_width)
@@ -401,6 +545,21 @@ def value(self) -> Any:
Returns:
Any: The whole value of the field, irrespective of overflow.
+
+ Example:
+ Use after configuring the field in `petition.overflow_fields`.
+
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ petition.overflow_fields["reasons"].value() }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ petition.overflow_fields["reasons"].value() }}
+ ```
"""
return self.value_if_defined()
@@ -430,6 +589,14 @@ def has_overflow(
Returns:
bool: True if the value's length exceeds the overflow trigger, False otherwise.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_has_overflow = petition.overflow_fields["reasons"].has_overflow()
+ ```
"""
if _original_value:
val = _original_value
@@ -478,6 +645,29 @@ def original_or_overflow_message(
Returns:
Union[str, List[Any]]: Either a string representing the overflow message or the original value.
+
+ Example:
+ With `reasons = "I need more time to move."` and
+ `petition.overflow_fields["reasons"].overflow_trigger = 16`:
+ Calling the field directly uses an empty overflow marker by default.
+
+ **Input (Mako)**
+
+ ```mako
+ ${ petition.overflow_fields["reasons"].original_or_overflow_message(overflow_message="See addendum.") }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ petition.overflow_fields["reasons"].original_or_overflow_message(overflow_message="See addendum.") }}
+ ```
+
+ **Output**
+
+ ```text
+ See addendum.
+ ```
"""
if _original_value:
val = _original_value
@@ -531,6 +721,29 @@ def safe_value(
Returns:
Union[str, List[Any]]: The portion of the variable that fits within the overflow trigger.
+
+ Example:
+ With `reasons = "I need more time to move."` and
+ `petition.overflow_fields["reasons"].overflow_trigger = 16`:
+ Calling the field directly uses an empty overflow marker by default.
+
+ **Input (Mako)**
+
+ ```mako
+ ${ petition.overflow_fields["reasons"].safe_value() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ petition.overflow_fields["reasons"].safe_value() }}
+ ```
+
+ **Output**
+
+ ```text
+ I need more time
+ ```
"""
# Handle simplest case first
if _original_value:
@@ -605,6 +818,21 @@ def value_if_defined(self) -> Any:
Returns:
Any: The value of the field if it exists, otherwise an empty string.
+
+ Example:
+ Use after configuring the field in `petition.overflow_fields`.
+
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ petition.overflow_fields["reasons"].value_if_defined() }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ petition.overflow_fields["reasons"].value_if_defined() }}
+ ```
"""
return showifdef(self.field_name, "")
@@ -637,6 +865,14 @@ def columns(
Note:
The "location" attribute of an Address object is always skipped in the column list.
+
+ Example:
+ After configuring `users` as an overflow field on `petition`:
+
+ ```yaml
+ code: |
+ addendum_columns = petition.overflow_fields["users"].columns()
+ ```
"""
if not skip_attributes:
skip_attributes = {"complete"}
@@ -691,6 +927,14 @@ def type(self) -> str:
Returns:
str: The type category of the value.
+
+ Example:
+ After configuring `users` as an overflow field on `petition`:
+
+ ```yaml
+ code: |
+ field_kind = petition.overflow_fields["users"].type()
+ ```
"""
value = self.value_if_defined()
if isinstance(value, list) or isinstance(value, DAList):
@@ -709,6 +953,14 @@ def is_list(self) -> bool:
Returns:
bool: True if the field contains a list, otherwise False.
+
+ Example:
+ After configuring `users` as an overflow field on `petition`:
+
+ ```yaml
+ code: |
+ field_is_list = petition.overflow_fields["users"].is_list()
+ ```
"""
return self.type() == "object_list" or self.type() == "list"
@@ -718,6 +970,14 @@ def is_object_list(self) -> bool:
Returns:
bool: True if the field contains a list of dictionaries or objects, otherwise False.
+
+ Example:
+ After configuring `users` as an overflow field on `petition`:
+
+ ```yaml
+ code: |
+ field_contains_people = petition.overflow_fields["users"].is_object_list()
+ ```
"""
return self.type() == "object_list"
@@ -731,6 +991,13 @@ def overflow_markdown(self) -> str:
Returns:
str: A markdown representation of the overflow values.
+
+ Example:
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ petition.overflow_fields["users"].overflow_markdown() }
+ ```
"""
columns = self.columns()
if not columns:
@@ -789,6 +1056,13 @@ def overflow_docx(
Returns:
A docx template with the inserted table.
+
+ Example:
+ In a DOCX addendum, give the path to your table template:
+
+ ```jinja2
+ {{p petition.overflow_fields["users"].overflow_docx(path="addendum_table.docx") }}
+ ```
"""
return include_docx_template(
path, columns=self.columns(), rows=self.overflow_value()
@@ -809,14 +1083,36 @@ class ALAddendumFieldDict(DAOrderedDict):
Attributes:
style (str): Determines the display behavior. If set to "overflow_only",.
only the overflow text will be displayed.
+
+ Example:
+ Configure the overflow fields on an ALDocument named `petition`:
+
+ ```yaml
+ code: |
+ petition.overflow_fields.from_list([
+ {"field_name": "reasons", "overflow_trigger": 640},
+ {"field_name": "users", "overflow_trigger": 2},
+ ])
+ petition.overflow_fields.gathered = True
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation. The `petition.overflow_fields` attribute is an ALAddendumFieldDict.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - petition: ALDocument.using(title="Petition", filename="petition", enabled=True, has_addendum=True)
+ ```
"""
super(ALAddendumFieldDict, self).init(*pargs, **kwargs)
self.object_type = ALAddendumField
@@ -841,6 +1137,15 @@ def initializeObject(self, *pargs, **kwargs) -> Any:
Returns:
The new dictionary entry created
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ petition.overflow_fields.initializeObject("reasons", ALAddendumField)
+ petition.overflow_fields["reasons"].overflow_trigger = 640
+ ```
"""
the_key = pargs[0]
newobj = super().initializeObject(*pargs, **kwargs)
@@ -854,6 +1159,18 @@ def from_list(self, data: List[Dict]) -> None:
Args:
data (list): List of dictionaries containing ield data with keys "field_name".
and "overflow_trigger".
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ petition.overflow_fields.from_list([
+ {"field_name": "reasons", "overflow_trigger": 640},
+ {"field_name": "users", "overflow_trigger": 2},
+ ])
+ petition.overflow_fields.gathered = True
+ ```
"""
for entry in data:
new_field = self.initializeObject(entry["field_name"], ALAddendumField)
@@ -871,6 +1188,14 @@ def defined_fields(self, style: str = "overflow_only") -> list:
Returns:
list: List of defined fields based on the specified style.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ addendum_fields = petition.overflow_fields.defined_fields()
+ ```
"""
if style == "overflow_only":
return [field for field in self.values() if len(field.overflow_value())]
@@ -883,6 +1208,14 @@ def overflow(self) -> list:
Returns:
list: A list of fields with overflow values.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ overflow_fields = petition.overflow_fields.overflow()
+ ```
"""
return self.defined_fields(style="overflow_only")
@@ -892,6 +1225,14 @@ def has_overflow(self) -> bool:
Returns:
bool: True if at least one field overflows, False otherwise.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_has_overflow = petition.overflow_fields.has_overflow()
+ ```
"""
for field in self.values():
if field.overflow_value():
@@ -986,90 +1327,113 @@ class ALDocument(DADict):
variable that is posed to the interview user to work around this
limitation.
- Examples: # TODO: the code blocks aren't working right yet on the Docusaurus page.
+ Examples:
+ Define an always-enabled petition with preview and final versions. With
+ `assembly_line.yml` included, `users[0]` is the first ALIndividual in
+ the `users` ALPeopleList. This attachment uses Mako:
- Simple use where the document is always enabled and will have no addendum
- --------------------------------------------------------------------------
- ```yaml
- ---
- objects:
- - my_doc: ALDocument.using(filename="myDoc.pdf", title="myDoc", enabled=True)
- ---
- attachment:
- variable name: my_doc[i] # This same template will be used for the `preview` and `final` keys
- content: |
- Here is some content
-
- % if i == 'final':
- ${ users[0].signature }
- % elif i == 'preview':
- [ Your signature here ]
- % endif
- ```
-
- Enable a document conditionally
- --------------------------------
- ```yaml
- ---
- # See that `enabled` is not defined here
- objects:
- - affidavit_of_indigency: ALDocument.using(filename="affidavit-of-indigency.pdf", title="Affidavit of Indigency")
- ---
- code: |
- affidavit_of_indigency.enabled = ask_indigency_questions and is_indigent
- ```
+ ```yaml
+ objects:
+ - petition: ALDocument.using(filename="petition.pdf", title="Petition", enabled=True)
+ ---
+ attachment:
+ variable name: petition[i]
+ content: |
+ # Petition
- An example enabling with a question posed to the interview user
- ----------------------------------------------------------------
- You should always use a code block or an object block to set the "enabled" status;
- Use an intermediate variable if you want to ask the user directly whether or not to include a document.
- ```yaml
- ---
- question: |
- Do you want the extra document included?
- fields:
- - no label: include_extra_document
- datatype: yesnoradio
- ---
- code: |
- extra_document.enabled = include_extra_document
- ---
- attachment:
- variable name: extra_document[i] # This same template will be used for `final` and `preview`
- docx template file: extra_document.docx
- ```
+ I, ${ users[0].name_full() }, request the relief described below.
- For a document that may need an addendum, you must specify this when the object is created
- or in a mandatory code block. The addendum will only be triggered if the document has "overflow"
- in one of the fields that you specify.
- ```
- ---
- objects:
- - my_doc: ALDocument.using(filename="myDoc.pdf", title="myDoc", enabled=True, has_addendum=True)
- ---
- attachment:
- variable name: my_doc[i]
- ...
- ---
- generic object: ALDocument
- attachment:
- variable name: x.addendum
- docx template file: docx_addendum.docx
- ---
- code: |
- my_doc.overflow_fields['big_text_variable'].overflow_trigger = 640 # Characters
- my_doc.overflow_fields['big_text_variable'].label = "Big text label" # Optional - you may use in your addendum
- my_doc.overflow_fields['list_of_objects_variable'].overflow_trigger = 4 # Items in the list
- my_doc.overflow_fields.gathered = True
- ```
+ ${ users[0].signature_if_final(i) }
+ ---
+ event: review_petition
+ question: |
+ Review your petition before signing
+ subquestion: |
+ ${ petition.as_pdf(key="preview") }
+ ---
+ event: download_petition
+ question: |
+ Download your signed petition
+ subquestion: |
+ ${ petition.as_pdf() }
+ ```
+
+ For a DOCX template, replace the attachment's `content` with
+ `docx template file: petition.docx`. Inside that file, use Jinja2:
+
+ ```jinja2
+ I, {{ users[0].name_full() }}, request the relief described below.
+
+ {{ users[0].signature_if_final(i) }}
+ ```
+
+ To make a document optional, derive `enabled` from a separate answer.
+ Ask about `include_extra_document` rather than asking about `enabled`
+ directly, because the bundle refreshes `enabled` when assembling:
+
+ ```yaml
+ objects:
+ - extra_document: ALDocument.using(filename="extra_document.pdf", title="Additional statement")
+ ---
+ question: |
+ Do you want to include an additional statement?
+ fields:
+ - Include a statement: include_extra_document
+ datatype: yesnoradio
+ ---
+ code: |
+ extra_document.enabled = include_extra_document
+ ---
+ attachment:
+ variable name: extra_document[i]
+ docx template file: extra_document.docx
+ ```
+
+ For a PDF template with a limited-size text field, configure overflow
+ and define an addendum. In this example, `reasons` is a gathered text
+ answer, and `reasons_field` is the field name in `petition.pdf`, stored
+ in your package's `data/templates` directory:
+
+ ```yaml
+ objects:
+ - petition: ALDocument.using(filename="petition.pdf", title="Petition", enabled=True, has_addendum=True)
+ ---
+ code: |
+ petition.overflow_fields["reasons"].overflow_trigger = 640
+ petition.overflow_fields["reasons"].label = "Reasons for the request"
+ petition.overflow_fields.gathered = True
+ ---
+ attachment:
+ variable name: petition[i]
+ pdf template file: petition.pdf
+ fields:
+ - reasons_field: ${ petition.safe_value("reasons") }
+ ---
+ attachment:
+ variable name: petition.addendum
+ content: |
+ # Additional reasons
+
+ ${ petition.overflow_value("reasons") }
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - petition: ALDocument.using(title="Petition", filename="petition", enabled=True)
+ ```
"""
super(ALDocument, self).init(*pargs, **kwargs)
self.initializeAttribute("overflow_fields", ALAddendumFieldDict)
@@ -1101,6 +1465,19 @@ def as_pdf(
Returns:
DAFile: Assembled document in PDF format, possibly combined with addendum.
+
+ Example:
+ Link to the assembled petition on the download screen (Mako):
+
+ ```mako
+ ${ petition.as_pdf() }
+ ```
+
+ Use the attachment’s preview version on a review screen:
+
+ ```mako
+ ${ petition.as_pdf(key="preview") }
+ ```
"""
# Trigger some stuff up front to avoid idempotency problems
self.title
@@ -1175,6 +1552,13 @@ def as_docx(
Returns:
DAFile: Assembled document in DOCX or PDF format.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ petition.as_docx() }
+ ```
"""
if append_matching_suffix and key == self.suffix_to_append:
filename = f"{base_name(self.filename)}_{key}"
@@ -1226,6 +1610,14 @@ def as_list(self, key: str = "final", refresh: bool = True) -> List[DAFile]:
Returns:
List[DAFile]: List containing the main document and possibly its addendum.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_files = petition.as_list()
+ ```
"""
if refresh:
if self.has_addendum and self.has_overflow():
@@ -1245,6 +1637,14 @@ def need_addendum(self) -> bool:
Returns:
bool: True if an addendum is needed, False otherwise.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ petition_needs_addendum = petition.need_addendum()
+ ```
"""
return (
hasattr(self, "has_addendum") and self.has_addendum and self.has_overflow()
@@ -1256,6 +1656,14 @@ def has_overflow(self) -> bool:
Returns:
bool: True if there are overflow fields, False otherwise.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_has_overflow = petition.has_overflow()
+ ```
"""
return self.overflow_fields.has_overflow()
@@ -1265,6 +1673,14 @@ def overflow(self) -> list:
Returns:
list: List of overflow fields.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ overflow_fields = petition.overflow()
+ ```
"""
return self.overflow_fields.overflow()
@@ -1302,6 +1718,29 @@ def original_or_overflow_message(
Returns:
Union[str, List[Any]]: Either the original value or the overflow message, never a truncated value.
+
+ Example:
+ With `reasons = "I need more time to move."` and
+ `petition.overflow_fields["reasons"].overflow_trigger = 16`:
+ `petition` uses the default overflow marker (`"..."`).
+
+ **Input (Mako)**
+
+ ```mako
+ ${ petition.original_or_overflow_message("reasons", overflow_message="See addendum.") }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ petition.original_or_overflow_message("reasons", overflow_message="See addendum.") }}
+ ```
+
+ **Output**
+
+ ```text
+ See addendum.
+ ```
"""
if overflow_message is None:
overflow_message = self.default_overflow_message
@@ -1336,6 +1775,29 @@ def safe_value(
Returns:
str: The "safe" value of the specified field.
+
+ Example:
+ With `reasons = "I need more time to move."` and
+ `petition.overflow_fields["reasons"].overflow_trigger = 16`:
+ `petition` uses the default overflow marker (`"..."`).
+
+ **Input (Mako)**
+
+ ```mako
+ ${ petition.safe_value("reasons") }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ petition.safe_value("reasons") }}
+ ```
+
+ **Output**
+
+ ```text
+ I need more...
+ ```
"""
if overflow_message is None:
overflow_message = self.default_overflow_message
@@ -1368,6 +1830,29 @@ def overflow_value(
Returns:
str: The "overflow" value of the specified field.
+
+ Example:
+ With `reasons = "I need more time to move."` and
+ `petition.overflow_fields["reasons"].overflow_trigger = 16`:
+ `petition` uses the default overflow marker (`"..."`).
+
+ **Input (Mako)**
+
+ ```mako
+ ${ petition.overflow_value("reasons") }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ petition.overflow_value("reasons") }}
+ ```
+
+ **Output**
+
+ ```text
+ time to move.
+ ```
"""
if overflow_message is None:
overflow_message = self.default_overflow_message
@@ -1392,6 +1877,14 @@ def is_enabled(self, refresh: bool = True) -> bool:
Returns:
bool: True if the document is enabled, otherwise False.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_is_enabled = petition.is_enabled()
+ ```
"""
if hasattr(self, "always_enabled") and self.always_enabled:
return True
@@ -1418,26 +1911,43 @@ class ALStaticDocument(DAStaticFile):
filename (str): Path to the file within `/data/static/`.
title (str): Title displayed as a row when invoking `download_list_html()` method from ALDocumentBundle.
- Examples:
- Add a static PDF file to a document bundle.
- .. code-block:: yaml
- ---
- objects:
- - static_test: ALStaticDocument.using(title="Static Test", filename="static.pdf", enabled=True)
- ---
- objects:
- - bundle: ALDocumentBundle.using(elements=[static_test], filename="bundle", title="Documents to download now")
Todo:
Consider handling files in `/data/templates` if deemed useful, potentially by copying into a DAFile using `pdf_concatenate()`.
+
+ Example:
+ Place `instructions.pdf` in your package’s `data/static` directory,
+ then add it to the user’s download bundle:
+
+ ```yaml
+ objects:
+ - instructions: ALStaticDocument.using(title="Instructions", filename="instructions.pdf", enabled=True)
+ - al_user_bundle: ALDocumentBundle.using(elements=[instructions], filename="user_bundle", title="Your documents", enabled=True)
+ ---
+ event: download
+ question: |
+ Download your instructions
+ subquestion: |
+ ${ al_user_bundle.download_list_html() }
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - instructions: ALStaticDocument.using(title="Instructions", filename="instructions.pdf", enabled=True)
+ ```
"""
super().init(*pargs, **kwargs)
self.has_addendum = False
@@ -1468,6 +1978,14 @@ def as_list(self, key: str = "final", refresh: bool = True) -> List[DAStaticFile
Returns:
List[DAStaticFile]: A list containing this document.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_files = instructions.as_list()
+ ```
"""
return [self]
@@ -1491,6 +2009,13 @@ def as_pdf(
Returns:
Union[DAStaticFile, DAFile]: The document in PDF format.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ instructions.as_pdf() }
+ ```
"""
if not filename:
filename = self.filename
@@ -1512,6 +2037,13 @@ def as_docx(
Returns:
Union[DAStaticFile, DAFile]: The document in DOCX or PDF format.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ instructions.as_docx() }
+ ```
"""
if self._is_docx():
return self
@@ -1552,19 +2084,35 @@ def show(self, **kwargs) -> DAFile:
Returns:
DAFile: Displayable version of the document.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ instructions.show() }
+ ```
"""
# TODO: this explicit conversion shouldn't be needed
# Workaround for problem generating thumbnails without it
return pdf_concatenate(self).show(**kwargs)
def is_enabled(self, **kwargs) -> bool:
- """Check if the document is enabled.
+ """
+ Check if the document is enabled.
Args:
**kwargs: Unused (for signature compatibility only).
Returns:
bool: True if the document is enabled, otherwise False.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_is_enabled = instructions.is_enabled()
+ ```
"""
return self.enabled
@@ -1650,31 +2198,39 @@ class ALDocumentBundle(DAList):
page_number_offset_vertical (float): Vertical inset in pixels from the nearest page.
edge for stamped page numbers. Defaults to `15`.
- Examples:
- Given three documents: `Cover page`, `Main motion form`, and `Notice of Interpreter Request`,
- bundle them as follows:
- ```
- bundle = ALDocumentBundle(elements=[cover_page, main_motion, notice_of_request],
- filename="documents_bundle", title="Document Set")
- ```
-
- Convert the bundle to a PDF:
- ```
- combined_pdf = bundle.as_pdf()
- ```
+ Example:
+ After defining `petition` and `instructions` as documents, add them
+ to the bundle and display download and email controls:
- Convert the bundle to a zip archive containing individual PDFs:
- ```
- zipped_files = bundle.as_zip()
- ```
+ ```yaml
+ objects:
+ - al_user_bundle: ALDocumentBundle.using(elements=[petition, instructions], filename="user_bundle.pdf", title="Your documents", enabled=True)
+ ---
+ event: download
+ question: |
+ Your documents are ready
+ subquestion: |
+ ${ al_user_bundle.download_list_html() }
+ ${ al_user_bundle.send_button_html() }
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - al_user_bundle: ALDocumentBundle.using(elements=[petition, instructions], title="Your documents", filename="user_bundle", enabled=True)
+ ```
"""
super().init(*pargs, **kwargs)
if "auto_gather" not in kwargs:
@@ -1730,6 +2286,19 @@ def as_pdf(
Returns:
Optional[DAFile]: Combined PDF file or None if no documents are enabled.
+
+ Example:
+ Link to the final combined PDF on the download screen (Mako):
+
+ ```mako
+ ${ al_user_bundle.as_pdf() }
+ ```
+
+ On a review screen, use the preview version before the user signs:
+
+ ```mako
+ ${ al_user_bundle.as_pdf(key="preview") }
+ ```
"""
safe_key = space_to_underscore(key)
if pdfa:
@@ -1869,6 +2438,19 @@ def as_zip(
Returns:
DAFile: A zip file containing the enabled documents.
+
+ Example:
+ Offer a ZIP of the final PDFs on the download screen (Mako):
+
+ ```mako
+ ${ al_user_bundle.as_zip() }
+ ```
+
+ Include editable versions where available alongside the PDFs:
+
+ ```mako
+ ${ al_user_bundle.as_zip(format="docx", include_pdf=True) }
+ ```
"""
if format is None:
format = "pdf"
@@ -1934,6 +2516,13 @@ def preview(self, refresh: bool = True) -> Optional[DAFile]:
Returns:
Optional[DAFile]: Preview PDF file or None if no documents are enabled.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ al_user_bundle.preview() }
+ ```
"""
return self.as_pdf(key="preview", refresh=refresh)
@@ -1946,6 +2535,14 @@ def has_enabled_documents(self, refresh=False) -> bool:
Returns:
bool: True if there's at least one enabled document, otherwise False.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ has_documents_to_download = al_user_bundle.has_enabled_documents()
+ ```
"""
return any(document.is_enabled(refresh=refresh) for document in self.elements)
@@ -1958,6 +2555,14 @@ def enabled_documents(self, refresh: bool = True) -> List[Any]:
Returns:
List[Any]: List of enabled documents.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ enabled_documents_result = al_user_bundle.enabled_documents()
+ ```
"""
return [
document
@@ -1975,6 +2580,14 @@ def as_flat_list(self, key: str = "final", refresh: bool = True) -> List[DAFile]
Returns:
List[DAFile]: Flattened list of enabled documents.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ download_files = al_user_bundle.as_flat_list()
+ ```
"""
# Iterate through the list of self.templates
# Unpack the list of documents at each step so this can be concatenated into a single list
@@ -1998,6 +2611,23 @@ def get_titles(self, key: str = "final", refresh: bool = True) -> List[str]:
Returns:
List[str]: Titles of the enabled documents.
+
+ Example:
+ With enabled documents titled "Petition" and "Instructions", in that
+ order, in `al_user_bundle`, the value of `document_titles` is:
+
+ **Input (interview YAML)**
+
+ ```yaml
+ code: |
+ document_titles = al_user_bundle.get_titles()
+ ```
+
+ **Output**
+
+ ```text
+ ['Petition', 'Instructions']
+ ```
"""
flat_list = []
for document in self.enabled_documents(refresh=refresh):
@@ -2022,6 +2652,14 @@ def as_pdf_list(
Returns:
List[DAFile]: List of enabled documents as individual PDFs.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ pdf_files = al_user_bundle.as_pdf_list()
+ ```
"""
return [
pdf
@@ -2046,6 +2684,14 @@ def as_docx_list(self, key: str = "final", refresh: bool = True) -> List[DAFile]
Returns:
List[DAFile]: List of documents represented as DOCX files or in their original format.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ editable_files = al_user_bundle.as_docx_list()
+ ```
"""
return [
docx
@@ -2070,6 +2716,14 @@ def as_editable_list(
Returns:
List[DAFile]: Flat list of documents in DOCX or RTF formats or their original format.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ editable_files = al_user_bundle.as_editable_list()
+ ```
"""
docs = self.as_flat_list(key=key, refresh=refresh)
editable = []
@@ -2125,6 +2779,14 @@ def get_cacheable_documents(
Returns:
Tuple[List[CacheableDocument], Optional[DAFile], Optional[DAFile]]: A list of dictionaries containing document titles, filenames, and files, a zip file of the whole bundle, and a PDF of the whole.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ cached_documents = al_user_bundle.get_cacheable_documents()
+ ```
"""
# reduce idempotency delays
enabled_docs = self.enabled_documents(refresh=refresh)
@@ -2223,6 +2885,14 @@ def has_broken_documents(self) -> bool:
Returns:
bool: True if any document or nested bundle has broken content.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ has_unreadable_uploads = al_user_bundle.has_broken_documents()
+ ```
"""
return len(self.broken_exhibit_titles()) > 0
@@ -2233,6 +2903,14 @@ def broken_exhibit_titles(self) -> List[str]:
Returns:
List[str]: Titles of exhibits that will be skipped.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ unreadable_exhibit_titles = al_user_bundle.broken_exhibit_titles()
+ ```
"""
titles: List[str] = []
for document in self.enabled_documents():
@@ -2250,6 +2928,13 @@ def broken_documents_warning_html(self) -> str:
Returns:
str: The warning HTML, or an empty string if nothing is broken.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ al_user_bundle.broken_documents_warning_html() }
+ ```
"""
broken_titles = self.broken_exhibit_titles()
if not broken_titles:
@@ -2324,6 +3009,31 @@ def download_list_html(
Returns:
str: HTML representation of a table with documents and their associated actions.
+
+ Example:
+ On the download screen, offer individual files and a combined PDF:
+
+ ```yaml
+ event: download
+ question: |
+ Your documents are ready
+ subquestion: |
+ ${ al_user_bundle.download_list_html(include_full_pdf=True) }
+ ```
+
+ To offer editable files where available, use
+ `${ al_user_bundle.download_list_html(format="docx") }` instead.
+
+ When you use background processing (recommended for multiple documents in a single bundle),
+ you can use the `use_previously_cached_files` parameter to speed up the download screen:
+
+ ```yaml
+ event: download
+ question: |
+ Your documents are ready
+ subquestion: |
+ ${ al_user_bundle.download_list_html(use_previously_cached_files=True) }
+ ```
"""
if not view_label:
view_label = str(self.view_label) or word("View")
@@ -2481,6 +3191,15 @@ def download_html(
Returns:
str: HTML representation of a table with documents and their associated actions.
+
+ Example:
+ For an older interview that uses this method (Mako):
+
+ ```mako
+ ${ al_user_bundle.download_html() }
+ ```
+
+ For new interviews, use `${ al_user_bundle.download_list_html() }`.
"""
log(
"ALDocumentBundle.download_html is deprecated; use download_list_html instead"
@@ -2548,6 +3267,13 @@ def send_email_table_row(
Returns:
str: The generated HTML string for the table row.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ al_user_bundle.send_email_table_row() }
+ ```
"""
if not send_label:
send_label = str(self.send_label) or word("Send")
@@ -2618,6 +3344,13 @@ def send_button_to_html(
Returns:
str: The generated HTML string for the button.
+
+ Example:
+ Offer a send button addressed to the first user’s gathered email:
+
+ ```mako
+ ${ al_user_bundle.send_button_to_html(users[0].email, label="Email my documents") }
+ ```
"""
if label is None:
label = str(self.send_label) or word("Send")
@@ -2692,6 +3425,14 @@ def send_button_html(
Returns:
str: The generated HTML string for the input box and button.
+
+ Example:
+ Let the user enter an email address and choose whether to include
+ editable files on the download screen:
+
+ ```mako
+ ${ al_user_bundle.send_button_html(preferred_formats=["pdf", "docx"]) }
+ ```
"""
if label is None:
label = str(self.send_label) or word("Send")
@@ -2792,6 +3533,14 @@ def send_email(
Returns:
bool: Indicates if the email was sent successfully.
+
+ Example:
+ In the code block for the user’s send-email action:
+
+ ```yaml
+ code: |
+ email_sent = al_user_bundle.send_email(to=users[0].email)
+ ```
"""
if editable is not None:
log(
@@ -2874,6 +3623,14 @@ def is_enabled(self, refresh=True) -> bool:
Returns:
bool: Indicates if the bundle and its child documents are enabled.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_is_enabled = al_user_bundle.is_enabled()
+ ```
"""
self_enabled = self._is_self_enabled(refresh=refresh)
return self_enabled and self.has_enabled_documents(refresh=refresh)
@@ -2910,6 +3667,13 @@ def as_docx(
Returns:
DAFile: A DAFile object containing the concatenated DOCX or PDF file.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ al_user_bundle.as_docx() }
+ ```
"""
if append_matching_suffix and key == self.suffix_to_append:
filename = f"{base_name(self.filename)}_{key}"
@@ -2943,6 +3707,14 @@ def as_list(self, key: str = "final", refresh: bool = True) -> List[DAFile]:
Returns:
List[DAFile]: A list of enabled DAFile objects.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_files = al_user_bundle.as_list()
+ ```
"""
return self.as_flat_list(key=key, refresh=refresh)
@@ -2957,14 +3729,37 @@ class ALExhibit(DAObject):
Will typically say something like "Exhibit 1"
label (str): A label, like "A" or "1" for this exhibit in the cover page and table of contents.
starting_page (int): first page number to use in table of contents.
+
+ Example:
+ With `exhibit_attachment` declared as an ALExhibitDocument, its
+ `exhibits` attribute is an ALExhibitList and each entry is an ALExhibit:
+
+ ```yaml
+ question: |
+ Upload your first exhibit
+ fields:
+ - Description: exhibit_attachment.exhibits[0].title
+ - Files: exhibit_attachment.exhibits[0].pages
+ datatype: files
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation. Each entry in `exhibit_attachment.exhibits` is an ALExhibit.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - exhibit_attachment: ALExhibitDocument.using(title="Exhibits", filename="exhibits")
+ ```
"""
super().init(*pargs, **kwargs)
self.initializeAttribute("_cache", DALazyAttribute)
@@ -3007,6 +3802,14 @@ def ocr_ready(self) -> bool:
Returns:
bool: True iff OCR process has finished on all pages.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ exhibit_text_is_ready = exhibit_attachment.exhibits[0].ocr_ready()
+ ```
"""
if hasattr(self, "ocr_status") and not self.ocr_status.ready():
return False
@@ -3020,6 +3823,14 @@ def ocr_pages(self) -> List[DAFile]:
Returns:
List[DAFile]: List of pages, either OCR-processed or original.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ searchable_pages = exhibit_attachment.exhibits[0].ocr_pages()
+ ```
"""
if (
hasattr(self, "ocr_version")
@@ -3046,6 +3857,14 @@ def is_broken(self) -> bool:
Returns:
bool: True if this exhibit will get skipped.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ exhibit_is_unreadable = exhibit_attachment.exhibits[0].is_broken()
+ ```
"""
if not getattr(self.pages, "gathered", False):
return False
@@ -3091,6 +3910,13 @@ def as_pdf(
Returns:
DAFile: PDF representation of the exhibit.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ exhibit_attachment.exhibits[0].as_pdf() }
+ ```
"""
safe_key = "_file"
if pdfa:
@@ -3137,13 +3963,22 @@ def num_pages(self) -> int:
Returns:
int: Total page count.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ exhibit_page_count = exhibit_attachment.exhibits[0].num_pages()
+ ```
"""
return self.pages.num_pages()
def toc_page_number(
self, toc_pages: int = 1, include_cover_page: bool = True
) -> int:
- """Return the page where this exhibit's uploaded content begins.
+ """
+ Return the page where this exhibit's uploaded content begins.
``start_page`` already includes the initial one-page table of contents and
every preceding exhibit cover. Adjust it only for additional TOC pages and
@@ -3155,6 +3990,14 @@ def toc_page_number(
Returns:
int: The physical page number of the exhibit's first uploaded page.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ exhibit_start_page = exhibit_attachment.exhibits[0].toc_page_number()
+ ```
"""
return self.start_page + toc_pages - 1 + int(include_cover_page)
@@ -3167,6 +4010,15 @@ def complete(self) -> bool:
Indicates if the exhibit is complete.
NOTE: This property always returns True after triggering the required attributes.
+
+ Example:
+ Gather the first exhibit’s title and uploaded pages in an interview
+ code block. This is a property, so do not add parentheses:
+
+ ```yaml
+ code: |
+ first_exhibit_complete = exhibit_attachment.exhibits[0].complete
+ ```
"""
self.title
self.pages.gather()
@@ -3191,7 +4043,7 @@ def ocrmypdf_task(
If the source file is an image (e.g., png, jpg, jpeg, gif), this function sets the image DPI to 300.
For non-image files, the text in the file is skipped during OCR.
- This function is designed to be executed as a background task (id: al_exhibit_ocr_pages_bg).
+ This function is designed to be executed as a background task (the `al_exhibit_ocr_pages` event in `al_document.yml`).
Args:
from_file (Union[DAFile, DAFileList]): The source file or list of files to be OCR-processed.
@@ -3202,6 +4054,22 @@ def ocrmypdf_task(
Raises:
subprocess.TimeoutExpired: If the ocrmypdf process takes longer than an hour.
+
+ Example:
+ Inside a background event, use `from_file` and `to_pdf` supplied by
+ `background_action()`; `to_pdf` must be an initialized DAFile:
+
+ ```yaml
+ event: al_exhibit_ocr_pages
+ code: |
+ from_file = action_argument("from_file")
+ to_pdf = action_argument("to_pdf")
+ background_response(ocrmypdf_task(from_file, to_pdf))
+ ```
+
+ AssemblyLine’s `al_document.yml` already provides this event. For
+ normal exhibit interviews, enable `auto_ocr` on the exhibit document
+ instead of adding a second event.
"""
if not from_file or not to_pdf:
log(
@@ -3245,14 +4113,34 @@ class ALExhibitList(DAList):
auto_labeler (Callable): An optional function or lambda to transform the exhibit's index to a label.
Uses A..Z labels by default.
auto_ocr (bool): If True, automatically starts OCR processing for uploaded exhibits. Defaults to False.
+
+ Example:
+ After gathering the exhibits on an ALExhibitDocument named
+ `exhibit_attachment`, display the combined file:
+
+ In question text (Mako):
+
+ ```mako
+ ${ exhibit_attachment.exhibits.as_pdf() }
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation. The `exhibit_attachment.exhibits` attribute is an ALExhibitList.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - exhibit_attachment: ALExhibitDocument.using(title="Exhibits", filename="exhibits")
+ ```
"""
super().init(*pargs, **kwargs)
if not hasattr(self, "auto_label"):
@@ -3307,6 +4195,13 @@ def as_pdf(
Returns:
DAFile: A single PDF containing all exhibits.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ exhibit_attachment.exhibits.as_pdf() }
+ ```
"""
if self.include_exhibit_cover_pages:
for exhibit in self:
@@ -3345,20 +4240,38 @@ def as_pdf(
)
def broken_exhibits(self) -> List["ALExhibit"]:
- """Returns exhibits that are complete but have no valid pages
+ """
+ Returns exhibits that are complete but have no valid pages
Returns:
List[ALExhibit]: The exhibits that will get skipped.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ unreadable_exhibits = exhibit_attachment.exhibits.broken_exhibits()
+ ```
"""
if not self.gathered:
return []
return [exhibit for exhibit in self if exhibit.is_broken()]
def has_broken_exhibits(self) -> bool:
- """True if any exhibit in this list will be skipped
+ """
+ True if any exhibit in this list will be skipped
Returns:
bool: True if at least one exhibit has no valid pages.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ has_unreadable_exhibits = exhibit_attachment.exhibits.has_broken_exhibits()
+ ```
"""
return len(self.broken_exhibits()) > 0
@@ -3371,6 +4284,14 @@ def size_in_bytes(self) -> int:
Returns:
int: Total size of all exhibits in bytes.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ exhibit_upload_size = exhibit_attachment.exhibits.size_in_bytes()
+ ```
"""
full_size = 0
for exhibit in self.complete_elements():
@@ -3403,6 +4324,14 @@ def ocr_ready(self) -> bool:
Returns:
bool: True if all exhibits are OCRed or if OCR hasn't started. False otherwise.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ exhibit_text_is_ready = exhibit_attachment.exhibits.ocr_ready()
+ ```
"""
ready = True
for exhibit in self.elements:
@@ -3438,6 +4367,15 @@ def hook_after_gather(self) -> None:
"""
Callback function executed after the entire list of exhibits is collected.
Manages auto-labeling and initiates OCR if necessary.
+
+ Example:
+ Docassemble calls this hook after gathering the list. In an interview,
+ trigger gathering rather than calling the hook yourself:
+
+ ```yaml
+ code: |
+ exhibit_attachment.exhibits.gather()
+ ```
"""
if len(self):
self._update_page_numbers()
@@ -3468,7 +4406,7 @@ class ALExhibitDocument(ALDocument):
(considering potential filesize constraints on emails).
Examples:
- ```
+ ```yaml
---
objects:
- exhibit_attachment: ALExhibitDocument.using(title="Exhibits", filename="exhibits" )
@@ -3483,7 +4421,7 @@ class ALExhibitDocument(ALDocument):
```
Example of using a custom label function, https://docassemble.org/docs/functions.html#item_label:
- ```
+ ```yaml
---
objects:
- exhibit_attachment: ALExhibitDocument.using(title="Exhibits", filename="exhibits" , auto_labeler=item_label)
@@ -3511,11 +4449,21 @@ class ALExhibitDocument(ALDocument):
page_number_offset_vertical: float
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - exhibit_attachment: ALExhibitDocument.using(title="Exhibits", filename="exhibits")
+ ```
"""
super().init(*pargs, **kwargs)
self.initializeAttribute("exhibits", ALExhibitList)
@@ -3572,6 +4520,14 @@ def has_overflow(self) -> bool:
Returns:
bool: Always False for this implementation.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_has_overflow = exhibit_attachment.has_overflow()
+ ```
"""
return False
@@ -3581,6 +4537,14 @@ def ocr_ready(self) -> bool:
Returns:
bool: True if all exhibits have been OCRed or if the OCR process hasn't been initiated.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ exhibit_text_is_ready = exhibit_attachment.ocr_ready()
+ ```
"""
return self.exhibits.ocr_ready()
@@ -3606,6 +4570,14 @@ def as_list(self, key: str = "final", refresh: bool = True) -> List[DAFile]:
Returns:
List[DAFile]: A list containing the document.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_files = exhibit_attachment.as_list()
+ ```
"""
return [self]
@@ -3631,6 +4603,13 @@ def as_pdf(
Returns:
DAFile: The document rendered as a PDF.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ exhibit_attachment.as_pdf() }
+ ```
"""
if not hasattr(self, "suffix_to_append"):
self.suffix_to_append = "preview"
@@ -3676,18 +4655,36 @@ def as_pdf(
return None
def has_broken_exhibits(self) -> bool:
- """True if any exhibit in this document will be silently skipped
+ """
+ True if any exhibit in this document will be silently skipped
Returns:
bool: True if at least one exhibit has no valid pages.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ has_unreadable_exhibits = exhibit_attachment.has_broken_exhibits()
+ ```
"""
return self.exhibits.has_broken_exhibits()
def broken_exhibits(self) -> List["ALExhibit"]:
- """Returns exhibits that are complete but have no valid pages
+ """
+ Returns exhibits that are complete but have no valid pages
Returns:
List[ALExhibit]: The exhibits that will get skipped.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ unreadable_exhibits = exhibit_attachment.broken_exhibits()
+ ```
"""
return self.exhibits.broken_exhibits()
@@ -3709,6 +4706,13 @@ def as_docx(
Returns:
DAFile: The document rendered as a PDF.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ exhibit_attachment.as_docx() }
+ ```
"""
return self.as_pdf()
@@ -3723,14 +4727,44 @@ class ALTableDocument(ALDocument):
suffix_to_append (str): Suffix that can be appended to file names, defaulting to "preview".
file (DAFile, optional): Reference to the generated file (can be PDF, DOCX, etc.).
table (???): Represents the actual table data. Type and attributes need more context to document.
+
+ Example:
+ In an interview:
+
+ ```yaml
+ objects:
+ - people_table: ALTableDocument.using(title="People", filename="people", enabled=True)
+ ---
+ table: people_table.table
+ rows: users
+ columns:
+ - Name: row_item.name_full()
+ - Address: row_item.address.on_one_line()
+ ---
+ event: download_people
+ question: |
+ Download the list of people
+ subquestion: |
+ ${ people_table.as_docx() }
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - people_table: ALTableDocument.using(title="People", filename="people", enabled=True)
+ ```
"""
super().init(*pargs, **kwargs)
self.has_addendum = False
@@ -3746,6 +4780,14 @@ def has_overflow(self) -> bool:
Returns:
bool: Always False for this implementation.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_has_overflow = people_table.has_overflow()
+ ```
"""
return False
@@ -3775,6 +4817,14 @@ def as_list(
Returns:
List[DAFile]: A list containing the document.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_files = people_table.as_list()
+ ```
"""
return [self[key]]
@@ -3798,6 +4848,13 @@ def as_pdf(
Returns:
DAFile: The table rendered as an XLSX spreadsheet.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ people_table.as_pdf() }
+ ```
"""
if not hasattr(self, "suffix_to_append"):
# When the key is "preview", append it to the file name
@@ -3827,6 +4884,13 @@ def as_docx(
Returns:
DAFile: The table rendered as an XLSX spreadsheet.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ people_table.as_docx() }
+ ```
"""
return self.as_pdf()
@@ -3840,14 +4904,36 @@ class ALUntransformedDocument(ALDocument):
Attributes:
has_addendum (bool): A flag indicating the presence of an addendum in the document.
suffix_to_append (str): Suffix that can be appended to file names, defaulting to "preview".
+
+ Example:
+ Wrap an existing DAFile named `uploaded_file` so it can be included
+ in an ALDocumentBundle:
+
+ ```yaml
+ objects:
+ - supporting_document: ALUntransformedDocument.using(title="Supporting document", filename="supporting_document", enabled=True)
+ ---
+ code: |
+ supporting_document["final"] = uploaded_file
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - supporting_document: ALUntransformedDocument.using(title="Supporting document", filename="supporting_document", enabled=True)
+ ```
"""
super().init(*pargs, **kwargs)
self.has_addendum = False
@@ -3862,6 +4948,14 @@ def has_overflow(self) -> bool:
Returns:
bool: Always False for this implementation.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_has_overflow = supporting_document.has_overflow()
+ ```
"""
return False
@@ -3877,6 +4971,14 @@ def as_list(
Returns:
List[DAFile]: A list containing the document.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ document_files = supporting_document.as_list()
+ ```
"""
return [self[key]]
@@ -3901,6 +5003,13 @@ def as_pdf(
Returns:
DAFile: The original, untransformed document.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ supporting_document.as_pdf() }
+ ```
"""
return self[key]
@@ -3922,6 +5031,13 @@ def as_docx(
Returns:
DAFile: The original, untransformed document.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ supporting_document.as_docx() }
+ ```
"""
return self[key]
@@ -3930,6 +5046,20 @@ class ALDocumentUpload(ALUntransformedDocument):
"""
Simplified class to handle uploaded documents, without any of the complexity of the
ALExhibitDocument class.
+
+ Example:
+ In an interview:
+
+ ```yaml
+ objects:
+ - supporting_document: ALDocumentUpload.using(title="Supporting document", filename="supporting_document", enabled=True)
+ ---
+ question: |
+ Upload your supporting document
+ fields:
+ - Document: supporting_document.file
+ datatype: file
+ ```
"""
def __getitem__(self, key):
@@ -3942,12 +5072,21 @@ def __getitem__(self, key):
def unpack_dafilelist(the_file: DAFileList) -> DAFile:
- """Creates a plain DAFile out of the first item in a DAFileList
+ """
+ Creates a plain DAFile out of the first item in a DAFileList
Args:
the_file (DAFileList): an item representing an uploaded document in a Docassemble interview.
Returns:
A DAFile representing the first item in the DAFileList, with a fixed instanceName attribute.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ supporting_file = unpack_dafilelist(uploaded_files)
+ ```
"""
if isinstance(the_file, DAFileList):
temp_name = the_file.instanceName
diff --git a/docassemble/AssemblyLine/al_general.py b/docassemble/AssemblyLine/al_general.py
index 3fd85f69..24647341 100644
--- a/docassemble/AssemblyLine/al_general.py
+++ b/docassemble/AssemblyLine/al_general.py
@@ -84,6 +84,22 @@ def safe_subdivision_type(country_code: str) -> Optional[str]:
Returns:
Optional[str]: The subdivision type for the country with the given country code.
+
+ Example:
+ The value of `address_region_label` for a US address:
+
+ **Input (interview YAML)**
+
+ ```yaml
+ code: |
+ address_region_label = safe_subdivision_type("US")
+ ```
+
+ **Output**
+
+ ```text
+ State
+ ```
"""
try:
return subdivision_type(country_code)
@@ -105,6 +121,32 @@ class with the `address_fields()` method and "smarter"
zip (str): The zip code where the person lives.
country (str): The country where the person lives.
impounded (Optional[bool]): Whether the address is impounded.
+
+ Example:
+ With `assembly_line.yml` included, `users` is an ALPeopleList,
+ `users[0]` is an ALIndividual, and `users[0].address` is an ALAddress.
+ For a custom list, declare it with an `objects` block as shown below.
+ Use these question blocks in your interview flow:
+
+ ```yaml
+ objects:
+ - users: ALPeopleList
+ ---
+ question: |
+ What is your name?
+ fields:
+ - code: users[0].name_fields()
+ ---
+ question: |
+ Where do you live?
+ fields:
+ - code: users[0].address.address_fields()
+ ---
+ question: |
+ Check your information
+ subquestion: |
+ ${ users[0].familiar() } lives at ${ users[0].address.on_one_line() }.
+ ```
"""
def address_fields(
@@ -152,6 +194,16 @@ def address_fields(
- Link to ISO-3166-1 alpha-2 codes:
[Officially assigned code elements](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements).
+
+ Example:
+ In an interview:
+
+ ```yaml
+ question: |
+ Where do you live?
+ fields:
+ - code: users[0].address.address_fields(default_state="MA")
+ ```
"""
# make sure the state name still returns a meaningful value if the interview country
# differs from the server's country.
@@ -312,6 +364,29 @@ def formatted_unit(
empty string. If the unit attribute exists and is not None or an empty string, the function will return
the unit number, possibly prefixed with 'Unit'. If the unit attribute exists and is None or an empty
string, the function will return an empty string.
+
+ Example:
+ With `users[0].address.address = "123 Main Street"`, `.unit = "2"`,
+ `.city = "Boston"`, `.state = "MA"`, `.zip = "02108"`, and `.country = "US"`
+ on the same address object, and English formatting:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].address.formatted_unit() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].address.formatted_unit() }}
+ ```
+
+ **Output**
+
+ ```text
+ Unit 2
+ ```
"""
if (
not hasattr(self, "unit")
@@ -364,7 +439,8 @@ def block(
long_state: bool = False,
show_impounded: bool = False,
) -> str:
- """Returns a one-line formatted address, primarily for geocoding.
+ """
+ Returns a one-line formatted address, primarily for geocoding.
Args:
language (str, optional): Language for the address format.
@@ -377,6 +453,25 @@ def block(
Returns:
str: The one-line formatted address.
+
+ Example:
+ With `users[0].address.address = "123 Main Street"`, `.unit = "2"`,
+ `.city = "Boston"`, `.state = "MA"`, `.zip = "02108"`, and `.country = "US"`
+ on the same address object, and English formatting:
+ The value of `address_text` contains Docassemble line-break markers.
+
+ **Input (interview YAML)**
+
+ ```yaml
+ code: |
+ address_text = users[0].address.block()
+ ```
+
+ **Output**
+
+ ```text
+ 123 Main Street [NEWLINE] Unit 2 [NEWLINE] Boston, MA 02108
+ ```
"""
if docassemble.base.functions.this_thread.evaluation_context == "docx":
line_breaker = ''
@@ -473,7 +568,8 @@ def line_one(
bare: bool = False,
show_impounded: bool = False,
) -> str:
- """Returns the first line of the address, including the unit number if it exists.
+ """
+ Returns the first line of the address, including the unit number if it exists.
Args:
language (str, optional): Language for the address format.
@@ -482,6 +578,29 @@ def line_one(
Returns:
str: The first line of the address.
+
+ Example:
+ With `users[0].address.address = "123 Main Street"`, `.unit = "2"`,
+ `.city = "Boston"`, `.state = "MA"`, `.zip = "02108"`, and `.country = "US"`
+ on the same address object, and English formatting:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].address.line_one() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].address.line_one() }}
+ ```
+
+ **Output**
+
+ ```text
+ 123 Main Street, Unit 2
+ ```
"""
if not show_impounded and (hasattr(self, "impounded") and self.impounded):
return str(self.impounded_output_label)
@@ -512,7 +631,8 @@ def line_two(
long_state: bool = False,
show_impounded: bool = False,
) -> str:
- """Returns the second line of the address, including city, state, and postal code.
+ """
+ Returns the second line of the address, including city, state, and postal code.
Args:
language (str, optional): Language for the address format.
@@ -521,6 +641,29 @@ def line_two(
Returns:
str: The second line of the address.
+
+ Example:
+ With `users[0].address.address = "123 Main Street"`, `.unit = "2"`,
+ `.city = "Boston"`, `.state = "MA"`, `.zip = "02108"`, and `.country = "US"`
+ on the same address object, and English formatting:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].address.line_two() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].address.line_two() }}
+ ```
+
+ **Output**
+
+ ```text
+ Boston, MA 02108
+ ```
"""
if not show_impounded and (hasattr(self, "impounded") and self.impounded):
return str(self.impounded_output_label)
@@ -557,7 +700,8 @@ def on_one_line(
long_state: bool = False,
show_impounded: bool = False,
) -> str:
- """Returns a one-line formatted address.
+ """
+ Returns a one-line formatted address.
Args:
include_unit (bool): If True, includes the unit in the formatted address. Defaults to True.
@@ -571,6 +715,29 @@ def on_one_line(
Returns:
str: The one-line formatted address.
+
+ Example:
+ With `users[0].address.address = "123 Main Street"`, `.unit = "2"`,
+ `.city = "Boston"`, `.state = "MA"`, `.zip = "02108"`, and `.country = "US"`
+ on the same address object, and English formatting (default country US):
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].address.on_one_line() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].address.on_one_line() }}
+ ```
+
+ **Output**
+
+ ```text
+ 123 Main Street, Unit 2, Boston, MA 02108
+ ```
"""
if not show_impounded and (hasattr(self, "impounded") and self.impounded):
return str(self.impounded_output_label)
@@ -631,10 +798,11 @@ def on_one_line(
return output
def normalized_address(self) -> Union[Address, "ALAddress"]:
- """Try geocoding the address, returning the normalized version if successful.
+ """
+ Try geocoding the address, returning the normalized version if successful.
If geocoding is successful, the method returns the "long" normalized version
- of the address. All methods, such as `my_address.normalized_address().block()`, are
+ of the address. All methods, such as `users[0].address.normalized_address().block()`, are
still available on the returned object. However, note that the returned object will
be a standard Address object, not an ALAddress object. If geocoding fails, it returns
the version of the address as entered by the user.
@@ -645,6 +813,21 @@ def normalized_address(self) -> Union[Address, "ALAddress"]:
Union[Address, "ALAddress"]:.
Normalized address if geocoding is successful, otherwise
the original address.
+
+ Example:
+ Format the returned address after attempting geocoding:
+
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ users[0].address.normalized_address().on_one_line() }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ users[0].address.normalized_address().on_one_line() }}
+ ```
"""
try:
self.geocode()
@@ -655,7 +838,8 @@ def normalized_address(self) -> Union[Address, "ALAddress"]:
return self
def state_name(self, country_code: Optional[str] = None) -> str:
- """Returns the full state name based on the state abbreviation.
+ """
+ Returns the full state name based on the state abbreviation.
If a `country_code` is provided, it will override the country attribute of the Address
object. Otherwise, the method uses, in order:
@@ -671,6 +855,29 @@ def state_name(self, country_code: Optional[str] = None) -> str:
Returns:
str: The full state name corresponding to the state abbreviation. If an error occurs.
or the full name cannot be determined, returns the state abbreviation.
+
+ Example:
+ With `users[0].address.address = "123 Main Street"`, `.unit = "2"`,
+ `.city = "Boston"`, `.state = "MA"`, `.zip = "02108"`, and `.country = "US"`
+ on the same address object, and English formatting:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].address.state_name() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].address.state_name() }}
+ ```
+
+ **Output**
+
+ ```text
+ Massachusetts
+ ```
"""
if country_code:
return state_name(self.state, country_code=country_code)
@@ -689,19 +896,45 @@ def state_name(self, country_code: Optional[str] = None) -> str:
class ALAddressList(DAList):
- """A class to store a list of ALAddress objects.
+ """
+ A class to store a list of ALAddress objects.
Extends the DAList class and specifically caters to ALAddress objects.
It provides methods to initialize the list and get a string representation
of the list in a formatted manner.
+
+ Example:
+ The list is initialized on each ALIndividual. After gathering an entry:
+
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ users[0].previous_addresses[0].on_one_line() }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ users[0].previous_addresses[0].on_one_line() }}
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - previous_addresses: ALAddressList
+ ```
"""
super(ALAddressList, self).init(*pargs, **kwargs)
self.object_type = ALAddress
@@ -719,17 +952,43 @@ def __str__(self) -> str:
class ALNameList(DAList):
- """A class to store a list of IndividualName objects.
+ """
+ A class to store a list of IndividualName objects.
Extends the DAList class and is tailored for IndividualName objects.
+
+ Example:
+ The list is initialized on each ALIndividual. After gathering an entry:
+
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ users[0].previous_names[0] }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ users[0].previous_names[0] }}
+ ```
"""
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation. Each person’s `previous_names` and `aliases` are ALNameLists.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - users: ALPeopleList
+ ```
"""
super().init(*pargs, **kwargs)
self.object_type = IndividualName
@@ -744,16 +1003,54 @@ def __str__(self) -> str:
class ALPeopleList(DAList):
- """Class to store a list of ALIndividual objects, representing people.
-
- For example, defendants, plaintiffs, or children."""
+ """
+ Class to store a list of ALIndividual objects, representing people.
+
+ For example, defendants, plaintiffs, or children.
+
+ Example:
+ With `assembly_line.yml` included, `users` is an ALPeopleList,
+ `users[0]` is an ALIndividual, and `users[0].address` is an ALAddress.
+ For a custom list, declare it with an `objects` block as shown below.
+ Use these question blocks in your interview flow:
+
+ ```yaml
+ objects:
+ - users: ALPeopleList
+ ---
+ question: |
+ What is your name?
+ fields:
+ - code: users[0].name_fields()
+ ---
+ question: |
+ Where do you live?
+ fields:
+ - code: users[0].address.address_fields()
+ ---
+ question: |
+ Check your information
+ subquestion: |
+ ${ users[0].familiar() } lives at ${ users[0].address.on_one_line() }.
+ ```
+ """
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - users: ALPeopleList
+ ```
"""
super(ALPeopleList, self).init(*pargs, **kwargs)
self.object_type = ALIndividual
@@ -761,7 +1058,8 @@ def init(self, *pargs, **kwargs) -> None:
def names_and_addresses_on_one_line(
self, comma_string: str = "; ", bare=False
) -> str:
- """Provide names and addresses of individuals on one line.
+ """
+ Provide names and addresses of individuals on one line.
Args:
comma_string (str, optional): The string to use between name-address pairs. Defaults to '; '.
@@ -769,6 +1067,19 @@ def names_and_addresses_on_one_line(
Returns:
str: Formatted string of names followed by addresses.
+
+ Example:
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ users.names_and_addresses_on_one_line() }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ users.names_and_addresses_on_one_line() }}
+ ```
"""
return comma_and_list(
[
@@ -787,7 +1098,8 @@ def names_and_addresses_on_one_line(
def familiar(
self, unique_names: Optional[list] = None, default: Optional[str] = None
) -> str:
- """Provide a list of familiar forms of names of individuals, in the
+ """
+ Provide a list of familiar forms of names of individuals, in the
most familiar way possible while preserving uniqueness. When possible,
it will return just the first name of each individual.
@@ -798,6 +1110,29 @@ def familiar(
default (str): The default name to use if a unique name is not available.
Returns:
str: Formatted string of familiar names.
+
+ Example:
+ With a gathered `users` list containing Alex Morgan Rivera, Jordan
+ Chen, and Taylor Brooks, in that order, with no suffixes or preferred
+ names, and the interview language set to English:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users.familiar() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users.familiar() }}
+ ```
+
+ **Output**
+
+ ```text
+ Alex, Jordan, and Taylor
+ ```
"""
return comma_and_list(
[
@@ -809,7 +1144,8 @@ def familiar(
def familiar_or(
self, unique_names: Optional[list] = None, default: Optional[str] = None
) -> str:
- """Provide a list of familiar forms of names of individuals separated by 'or',
+ """
+ Provide a list of familiar forms of names of individuals separated by 'or',
using the most familiar form possible while preserving uniqueness. When possible, it will return just the first name of each individual.
See ALIndividual.familiar for how the familiar form of each individual is determined.
@@ -820,6 +1156,29 @@ def familiar_or(
Returns:
str: Formatted string of familiar names separated by 'or'.
+
+ Example:
+ With a gathered `users` list containing Alex Morgan Rivera, Jordan
+ Chen, and Taylor Brooks, in that order, with no suffixes or preferred
+ names, and the interview language set to English:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users.familiar_or() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users.familiar_or() }}
+ ```
+
+ **Output**
+
+ ```text
+ Alex, Jordan, or Taylor
+ ```
"""
return comma_and_list(
[
@@ -830,7 +1189,8 @@ def familiar_or(
)
def short_list(self, limit: int, truncate_string: str = ", et al.") -> str:
- """Return a subset of the list, truncated with 'et al.' if it exceeds a given limit.
+ """
+ Return a subset of the list, truncated with 'et al.' if it exceeds a given limit.
Args:
limit (int): The maximum number of items to display before truncating.
@@ -838,6 +1198,29 @@ def short_list(self, limit: int, truncate_string: str = ", et al.") -> str:
Returns:
str: Formatted string of names, truncated if needed.
+
+ Example:
+ With a gathered `users` list containing Alex Morgan Rivera, Jordan
+ Chen, and Taylor Brooks, in that order, with no suffixes or preferred
+ names, and the interview language set to English:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users.short_list(limit=2) }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users.short_list(limit=2) }}
+ ```
+
+ **Output**
+
+ ```text
+ Alex M. Rivera and Jordan Chen, et al.
+ ```
"""
if len(self) > limit:
return comma_and_list(self[:limit]) + truncate_string
@@ -847,7 +1230,8 @@ def short_list(self, limit: int, truncate_string: str = ", et al.") -> str:
def full_names(
self, comma_string: str = ", ", and_string: Optional[str] = None
) -> str:
- """Return a formatted list of full names of individuals.
+ """
+ Return a formatted list of full names of individuals.
Args:
comma_string (str, optional): The string to use between names. Defaults to ','.
@@ -855,6 +1239,29 @@ def full_names(
Returns:
str: Formatted string of full names.
+
+ Example:
+ With a gathered `users` list containing Alex Morgan Rivera, Jordan
+ Chen, and Taylor Brooks, in that order, with no suffixes or preferred
+ names, and the interview language set to English:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users.full_names() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users.full_names() }}
+ ```
+
+ **Output**
+
+ ```text
+ Alex Morgan Rivera, Jordan Chen, and Taylor Brooks
+ ```
"""
if not and_string:
and_string = word("and")
@@ -872,7 +1279,8 @@ def full_names(
)
def pronoun_reflexive(self, **kwargs) -> str:
- """Returns the appropriate reflexive pronoun for the list of people, depending
+ """
+ Returns the appropriate reflexive pronoun for the list of people, depending
on the `person` keyword argument and the number of items in the list.
If the list is singular, return the reflexive pronoun for the first item in the list.
@@ -885,6 +1293,29 @@ def pronoun_reflexive(self, **kwargs) -> str:
Returns:
str: The reflexive pronoun for the list.
+
+ Example:
+ With a gathered `users` list containing Alex Morgan Rivera, Jordan
+ Chen, and Taylor Brooks, in that order, with no suffixes or preferred
+ names, and the interview language set to English:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users.pronoun_reflexive(person=3) }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users.pronoun_reflexive(person=3) }}
+ ```
+
+ **Output**
+
+ ```text
+ themselves
+ ```
"""
person = str(kwargs.get("person", self.get_point_of_view()))
@@ -913,7 +1344,8 @@ def pronoun_reflexive(self, **kwargs) -> str:
class ALIndividual(Individual):
- """Used to represent an Individual on the assembly line project.
+ """
+ Used to represent an Individual on the assembly line project.
This class extends the Individual class and adds more tailored attributes and methods
relevant for the assembly line project. Specifically, it has attributes for previous addresses,
@@ -931,6 +1363,32 @@ class ALIndividual(Individual):
Note:
Objects as attributes should not be passed directly to the constructor due to
initialization requirements in the Docassemble framework. See the `init` method.
+
+ Example:
+ With `assembly_line.yml` included, `users` is an ALPeopleList,
+ `users[0]` is an ALIndividual, and `users[0].address` is an ALAddress.
+ For a custom list, declare it with an `objects` block as shown below.
+ Use these question blocks in your interview flow:
+
+ ```yaml
+ objects:
+ - users: ALPeopleList
+ ---
+ question: |
+ What is your name?
+ fields:
+ - code: users[0].name_fields()
+ ---
+ question: |
+ Where do you live?
+ fields:
+ - code: users[0].address.address_fields()
+ ---
+ question: |
+ Check your information
+ subquestion: |
+ ${ users[0].familiar() } lives at ${ users[0].address.on_one_line() }.
+ ```
"""
previous_addresses: ALAddressList
@@ -942,11 +1400,21 @@ class ALIndividual(Individual):
preferred_name: IndividualName
def init(self, *pargs, **kwargs) -> None:
- """Standard DAObject init method.
+ """
+ Standard DAObject init method.
Args:
*pargs: Positional arguments.
**kwargs: Keyword arguments.
+
+ Example:
+ Docassemble calls `init()` automatically during object creation. Each entry, such as `users[0]`, is an ALIndividual.
+ See the class example for the rest of the setup.
+
+ ```yaml
+ objects:
+ - users: ALPeopleList
+ ```
"""
super(ALIndividual, self).init(*pargs, **kwargs)
# Initialize the attributes that are themselves objects. Requirement to work with Docassemble
@@ -971,13 +1439,30 @@ def init(self, *pargs, **kwargs) -> None:
self.initializeAttribute("preferred_name", IndividualName)
def signature_if_final(self, i: str) -> Union[DAFile, str]:
- """Returns the individual's signature if `i` is "final", which usually means we are assembling the final version of the document (as opposed to a preview).
+ """
+ Returns the individual's signature if `i` is "final", which usually means we are assembling the final version of the document (as opposed to a preview).
Args:
i (str): The condition which, if set to "final", returns the signature.
Returns:
Union[DAFile, str]: The signature if the condition is met, otherwise an empty string.
+
+ Example:
+ Use in an attachment whose variable name is `petition[i]`. The `i`
+ value distinguishes the final document from its preview.
+
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ users[0].signature_if_final(i) }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ users[0].signature_if_final(i) }}
+ ```
"""
if i == "final":
return self.signature
@@ -987,7 +1472,8 @@ def signature_if_final(self, i: str) -> Union[DAFile, str]:
def phone_numbers(
self, country: Optional[str] = None, show_impounded: bool = False
) -> str:
- """Fetches and formats the phone numbers of the individual.
+ """
+ Fetches and formats the phone numbers of the individual.
Supports the following attributes:
@@ -1003,6 +1489,29 @@ def phone_numbers(
Returns:
str: Formatted string of phone numbers.
+
+ Example:
+ With `users[0].mobile_number = "2025550123"` and
+ `users[0].phone_number = "2025550198"`, no other phone numbers, and
+ `users[0].phone_impounded = False`, both numbers are labeled:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].phone_numbers(country="US") }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].phone_numbers(country="US") }}
+ ```
+
+ **Output**
+
+ ```text
+ (202) 555-0123 (cell), (202) 555-0198 (other)
+ ```
"""
nums = []
if hasattr(self, "mobile_number") and self.mobile_number:
@@ -1072,10 +1581,24 @@ def phone_numbers(
assert False # We should never get here, no default return is necessary
def contact_methods(self) -> str:
- """Generates a formatted string of all provided contact methods.
+ """
+ Generates a formatted string of all provided contact methods.
Returns:
str: A formatted string indicating the available methods to contact the individual.
+
+ Example:
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ users[0].contact_methods() }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ users[0].contact_methods() }}
+ ```
"""
methods = []
if self.phone_numbers():
@@ -1097,12 +1620,21 @@ def contact_methods(self) -> str:
)
def merge_letters(self, new_letters: str) -> None:
- """If the Individual has a child_letters attribute, add the new letters to the existing list
+ """
+ If the Individual has a child_letters attribute, add the new letters to the existing list
Avoid using. Only used in 209A.
Args:
new_letters (str): The new letters to add to the existing list of letters.
+
+ Example:
+ For a legacy 209A interview that uses `child_letters`:
+
+ ```yaml
+ code: |
+ users[0].merge_letters("ab")
+ ```
"""
# TODO: move to 209A package
if hasattr(self, "child_letters"):
@@ -1111,10 +1643,24 @@ def merge_letters(self, new_letters: str) -> None:
self.child_letters = filter_letters(new_letters)
def formatted_age(self) -> str:
- """Calculates and formats the age of the individual based on their birthdate.
+ """
+ Calculates and formats the age of the individual based on their birthdate.
Returns:
str: Formatted age string that shows the most relevant time unit; for example, if under 2 years, it will return "X months".
+
+ Example:
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ users[0].formatted_age() }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ users[0].formatted_age() }}
+ ```
"""
dd = date_difference(self.birthdate)
if dd.years >= 2:
@@ -1126,10 +1672,26 @@ def formatted_age(self) -> str:
return "%d days" % (int(dd.days),)
def normalized_address(self) -> Union[Address, ALAddress]:
- """Fetches the normalized version of the address.
+ """
+ Fetches the normalized version of the address.
Returns:
Union[Address, ALAddress]: The normalized address object.
+
+ Example:
+ Format the returned address after attempting geocoding:
+
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ users[0].normalized_address().on_one_line() }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ users[0].normalized_address().on_one_line() }}
+ ```
"""
return self.address.normalized_address()
@@ -1172,6 +1734,16 @@ def name_fields(
Note:
If `person_or_business` is set to None, the method will offer the end user a choice
and will set appropriate "show ifs" conditions for each type.
+
+ Example:
+ In an interview:
+
+ ```yaml
+ question: |
+ What is your name?
+ fields:
+ - code: users[0].name_fields(show_suffix=True)
+ ```
"""
if title_options:
log(
@@ -1347,6 +1919,16 @@ def address_fields(
Returns:
List[Dict[str, str]]: A list of dictionaries with field prompts for addresses.
+
+ Example:
+ In an interview:
+
+ ```yaml
+ question: |
+ Where do you live?
+ fields:
+ - code: users[0].address_fields(default_state="MA")
+ ```
"""
# TODO make this more flexible to work w/ homeless individuals and
# international addresses
@@ -1386,6 +1968,16 @@ def gender_fields(
Note:
self-described will provide an input that overrides the value of `gender` and is not persisted.
+
+ Example:
+ In an interview:
+
+ ```yaml
+ question: |
+ What is your gender?
+ fields:
+ - code: users[0].gender_fields(show_help=True)
+ ```
"""
if not choices:
choices = [
@@ -1460,6 +2052,16 @@ def pronoun_fields(
Returns:
List[Dict[str, str]]: A list of dictionaries with field prompts for pronouns.
+
+ Example:
+ In an interview:
+
+ ```yaml
+ question: |
+ What pronouns do you use?
+ fields:
+ - code: users[0].pronoun_fields(show_unknown=True)
+ ```
"""
if choices:
shuffled_choices = choices
@@ -1519,6 +2121,23 @@ def get_pronouns(self) -> set:
Returns:
set: A set of strings representing the individual's pronouns.
+
+ Example:
+ With `users[0].pronouns = "they/them/theirs"`, the value of
+ `selected_pronouns` is a Python set:
+
+ **Input (interview YAML)**
+
+ ```yaml
+ code: |
+ selected_pronouns = users[0].get_pronouns()
+ ```
+
+ **Output**
+
+ ```text
+ {'they/them/theirs'}
+ ```
"""
if hasattr(self, "pronouns") and isinstance(self.pronouns, str):
return {self.pronouns}
@@ -1536,6 +2155,27 @@ def list_pronouns(self) -> str:
Returns:
str: A formatted string of the individual's pronouns.
+
+ Example:
+ With `users[0].pronouns = "they/them/theirs"`:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].list_pronouns() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].list_pronouns() }}
+ ```
+
+ **Output**
+
+ ```text
+ they/them/theirs
+ ```
"""
return comma_list(sorted(self.get_pronouns()))
@@ -1559,6 +2199,16 @@ def language_fields(
Returns:
List[Dict[str, str]]: A list of dictionaries with field prompts for language preferences.
+
+ Example:
+ In an interview:
+
+ ```yaml
+ question: |
+ What language do you prefer?
+ fields:
+ - code: users[0].language_fields()
+ ```
"""
if not choices:
choices = [
@@ -1612,6 +2262,27 @@ def language_name(self) -> str:
str: The human-readable version of the language. If 'other' is selected,.
it returns the value in `language_other`. Otherwise, it uses the
`language_name` function.
+
+ Example:
+ With `users[0].language = "es"`:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].language_name() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].language_name() }}
+ ```
+
+ **Output**
+
+ ```text
+ Spanish
+ ```
"""
if self.language == "other":
return self.language_other
@@ -1625,6 +2296,17 @@ def gender_male(self) -> bool:
Used to assist with checkbox filling in PDFs with "skip undefined"
turned on.
+
+ Example:
+ In a DOCX template, conditionally include text based on the gathered gender:
+
+ ```jinja2
+ {% if users[0].gender_male %}
+ [ X ]
+ {% else %}
+ [ ]
+ {% endif %}
+ ```
"""
return self.gender.lower() == "male"
@@ -1635,6 +2317,17 @@ def gender_female(self) -> bool:
Used to assist with checkbox filling in PDFs with "skip undefined"
turned on.
+
+ Example:
+ In a DOCX template, conditionally include text based on the gathered gender:
+
+ ```jinja2
+ {% if users[0].gender_female %}
+ [ X ]
+ {% else %}
+ [ ]
+ {% endif %}
+ ```
"""
return self.gender.lower() == "female"
@@ -1645,6 +2338,17 @@ def gender_other(self) -> bool:
Used to assist with checkbox filling in PDFs with "skip undefined"
turned on.
+
+ Example:
+ In a DOCX template, conditionally include text based on the gathered gender:
+
+ ```jinja2
+ {% if users[0].gender_other %}
+ [ X ]
+ {% else %}
+ [ ]
+ {% endif %}
+ ```
"""
return (self.gender != "male") and (self.gender != "female")
@@ -1655,6 +2359,17 @@ def gender_nonbinary(self) -> bool:
Used to assist with checkbox filling in PDFs with "skip undefined"
turned on.
+
+ Example:
+ In a DOCX template, conditionally include text based on the gathered gender:
+
+ ```jinja2
+ {% if users[0].gender_nonbinary %}
+ [ X ]
+ {% else %}
+ [ ]
+ {% endif %}
+ ```
"""
return self.gender.lower() == "nonbinary"
@@ -1665,6 +2380,17 @@ def gender_unknown(self) -> bool:
Used to assist with checkbox filling in PDFs with "skip undefined"
turned on.
+
+ Example:
+ In a DOCX template, conditionally include text based on the gathered gender:
+
+ ```jinja2
+ {% if users[0].gender_unknown %}
+ [ X ]
+ {% else %}
+ [ ]
+ {% endif %}
+ ```
"""
return self.gender.lower() == "unknown"
@@ -1675,6 +2401,17 @@ def gender_undisclosed(self) -> bool:
Used to assist with checkbox filling in PDFs with "skip undefined"
turned on.
+
+ Example:
+ In a DOCX template, conditionally include text based on the gathered gender:
+
+ ```jinja2
+ {% if users[0].gender_undisclosed %}
+ [ X ]
+ {% else %}
+ [ ]
+ {% endif %}
+ ```
"""
return self.gender.lower() == "prefer-not-to-say"
@@ -1685,6 +2422,17 @@ def gender_self_described(self) -> bool:
Used to assist with checkbox filling in PDFs with "skip undefined"
turned on.
+
+ Example:
+ In a DOCX template, conditionally include text based on the gathered gender:
+
+ ```jinja2
+ {% if users[0].gender_self_described %}
+ [ X ]
+ {% else %}
+ [ ]
+ {% endif %}
+ ```
"""
return self.gender not in [
"prefer-not-to-say",
@@ -1697,6 +2445,20 @@ def gender_self_described(self) -> bool:
def contact_fields(self) -> None:
"""
Return field prompts for other contact info
+
+ Example:
+ This method is a placeholder and returns `None`. Use explicit fields instead:
+
+ ```yaml
+ question: |
+ How can we contact you?
+ fields:
+ - Email: users[0].email
+ datatype: email
+ required: False
+ - Mobile phone: users[0].mobile_number
+ required: False
+ ```
"""
pass
@@ -1706,6 +2468,29 @@ def initials(self) -> str:
Returns the initials of the individual as a string.
For example, "Quinten K Steenhuis" would return "QKS".
+
+ Example:
+ With `users[0].name.first = "Alex"`, `users[0].name.middle = "Morgan"`,
+ and `users[0].name.last = "Rivera"`, and no suffix or preferred name:
+ `initials` is a property, so do not add parentheses.
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].initials }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].initials }}
+ ```
+
+ **Output**
+
+ ```text
+ AMR
+ ```
"""
return f"{self.name.first[:1]}{self.name.middle[:1] if hasattr(self.name,'middle') else ''}{self.name.last[:1] if hasattr(self.name, 'last') else ''}"
@@ -1729,6 +2514,19 @@ def address_block(
Returns:
str: The formatted address block.
+
+ Example:
+ In question or Markdown attachment text (Mako):
+
+ ```mako
+ ${ users[0].address_block() }
+ ```
+
+ In a DOCX template (Jinja2):
+
+ ```jinja2
+ {{ users[0].address_block() }}
+ ```
"""
if docassemble.base.functions.this_thread.evaluation_context == "docx":
if isinstance(self.address, ALAddress):
@@ -1775,7 +2573,8 @@ def address_block(
)
def pronoun(self, **kwargs) -> str:
- """Returns an objective pronoun as appropriate, based on the user's `pronouns` attribute or `gender` attribute.
+ """
+ Returns an objective pronoun as appropriate, based on the user's `pronouns` attribute or `gender` attribute.
The pronoun could be "I", "you," "her," "him," "it," or "them", or a user-provided pronoun.
If the user has selected multiple pronouns, each will appear, separated by a "/".
@@ -1789,6 +2588,28 @@ def pronoun(self, **kwargs) -> str:
- default (Optional[str]): The default word to use if the pronoun is not defined, e.g. "the agent". If not defined, the default term is the user's name.
Returns:
str: The appropriate pronoun.
+
+ Example:
+ With `users[0].pronouns = "they/them/theirs"`, an English interview,
+ and a third-person reference to `users[0]` (not the special `user` object):
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].pronoun(person=3) }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].pronoun(person=3) }}
+ ```
+
+ **Output**
+
+ ```text
+ them
+ ```
"""
person = str(kwargs.get("person", self.get_point_of_view()))
@@ -1859,13 +2680,36 @@ def pronoun(self, **kwargs) -> str:
return output
def pronoun_objective(self, **kwargs) -> str:
- """Returns the same pronoun as the `pronoun()` method.
+ """
+ Returns the same pronoun as the `pronoun()` method.
Args:
**kwargs: Additional keyword arguments.
Returns:
str: The appropriate objective pronoun.
+
+ Example:
+ With `users[0].pronouns = "they/them/theirs"`, an English interview,
+ and a third-person reference to `users[0]` (not the special `user` object):
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].pronoun_objective(person=3) }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].pronoun_objective(person=3) }}
+ ```
+
+ **Output**
+
+ ```text
+ them
+ ```
"""
return self.pronoun(**kwargs)
@@ -1888,6 +2732,28 @@ def pronoun_possessive(self, target, **kwargs) -> str:
Returns:
str: The appropriate possessive phrase, e.g., "her book", "their document".
+
+ Example:
+ With `users[0].pronouns = "they/them/theirs"`, an English interview,
+ and a third-person reference to `users[0]` (not the special `user` object):
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].pronoun_possessive("address", person=3) }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].pronoun_possessive("address", person=3) }}
+ ```
+
+ **Output**
+
+ ```text
+ their address
+ ```
"""
person = str(kwargs.get("person", self.get_point_of_view()))
@@ -1963,7 +2829,8 @@ def pronoun_possessive(self, target, **kwargs) -> str:
return output
def pronoun_subjective(self, **kwargs) -> str:
- """Returns a subjective pronoun, based on attributes.
+ """
+ Returns a subjective pronoun, based on attributes.
The pronoun could be "you," "we", "she," "he," "it," or "they". It depends
on the `gender` and `person_type` attributes and whether the individual
@@ -1975,6 +2842,28 @@ def pronoun_subjective(self, **kwargs) -> str:
- default (Optional[str]): The default word to use if the pronoun is not defined, e.g. "the agent". If not defined, the default term is the user's name.
Returns:
str: The appropriate subjective pronoun.
+
+ Example:
+ With `users[0].pronouns = "they/them/theirs"`, an English interview,
+ and a third-person reference to `users[0]` (not the special `user` object):
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].pronoun_subjective(person=3) }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].pronoun_subjective(person=3) }}
+ ```
+
+ **Output**
+
+ ```text
+ they
+ ```
"""
person = str(kwargs.get("person", self.get_point_of_view()))
@@ -2044,7 +2933,8 @@ def pronoun_subjective(self, **kwargs) -> str:
return output
def pronoun_reflexive(self, **kwargs) -> str:
- """Returns the appropriate reflexive pronoun ("herself", "themself", "myself"), based on the user's pronouns or gender and whether we are asked
+ """
+ Returns the appropriate reflexive pronoun ("herself", "themself", "myself"), based on the user's pronouns or gender and whether we are asked
to return a 1st, 2nd, or 3rd person pronoun.
Note that if the person has pronouns of they/them/theirs or a nonbinary gender, we return "themself" as the singular non-gendered
@@ -2058,6 +2948,28 @@ def pronoun_reflexive(self, **kwargs) -> str:
Returns:
str: The appropriate reflexive pronoun.
+
+ Example:
+ With `users[0].pronouns = "they/them/theirs"`, an English interview,
+ and a third-person reference to `users[0]` (not the special `user` object):
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].pronoun_reflexive(person=3) }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].pronoun_reflexive(person=3) }}
+ ```
+
+ **Output**
+
+ ```text
+ themself
+ ```
"""
person = str(kwargs.get("person", self.get_point_of_view()))
@@ -2148,7 +3060,8 @@ def pronoun_reflexive(self, **kwargs) -> str:
return output
def name_full(self) -> str:
- """Returns the individual's full name.
+ """
+ Returns the individual's full name.
If the person has the attribute person_type and it is defined
as either `business` or `organization`, it will only return
@@ -2156,6 +3069,28 @@ def name_full(self) -> str:
Returns:
str: The individual or business's full name.
+
+ Example:
+ With `users[0].name.first = "Alex"`, `users[0].name.middle = "Morgan"`,
+ and `users[0].name.last = "Rivera"`, and no suffix or preferred name:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].name_full() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].name_full() }}
+ ```
+
+ **Output**
+
+ ```text
+ Alex Morgan Rivera
+ ```
"""
if hasattr(self, "person_type") and self.person_type in [
"business",
@@ -2176,6 +3111,28 @@ def name_initials(self) -> str:
Returns:
str: The individual's name with the middle name as an initial.
+
+ Example:
+ With `users[0].name.first = "Alex"`, `users[0].name.middle = "Morgan"`,
+ and `users[0].name.last = "Rivera"`, and no suffix or preferred name:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].name_initials() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].name_initials() }}
+ ```
+
+ **Output**
+
+ ```text
+ Alex M. Rivera
+ ```
"""
if hasattr(self, "person_type") and self.person_type in [
"business",
@@ -2196,6 +3153,28 @@ def name_short(self) -> str:
Returns:
str: The individual'.
+
+ Example:
+ With `users[0].name.first = "Alex"`, `users[0].name.middle = "Morgan"`,
+ and `users[0].name.last = "Rivera"`, and no suffix or preferred name:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].name_short() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].name_short() }}
+ ```
+
+ **Output**
+
+ ```text
+ Alex Rivera
+ ```
"""
if hasattr(self, "person_type") and self.person_type in [
"business",
@@ -2237,9 +3216,43 @@ def familiar(
str: The individual's name in the most familiar form possible.
Example:
- ```mako
- Who do you want to take care of ${ children.familiar(unique_names=parents + petitioners, default="the minor") }
- ```
+ With `users[0].name.first = "Alex"`, `users[0].name.middle = "Morgan"`,
+ and `users[0].name.last = "Rivera"`, and no suffix or preferred name:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ users[0].familiar() }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ users[0].familiar() }}
+ ```
+
+ **Output**
+
+ ```text
+ Alex
+ ```
+
+ When a child and a parent may share a first name, include the other
+ people to compare against. If `children[0]` is Alex Rivera and
+ `users[0]` is Alex Chen (neither has a middle name, suffix, or preferred
+ name), the method uses the child's last name to distinguish them:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ children[0].familiar(unique_names=users, default="the minor") }
+ ```
+
+ **Output**
+
+ ```text
+ Alex Rivera
+ ```
"""
if hasattr(self, "person_type") and self.person_type in [
"business",
@@ -2331,13 +3344,22 @@ def __str__(self) -> str:
# (DANav isn't in public DA API, but currently in functions.py)
def section_links(nav) -> List[str]: # type: ignore
- """Returns a list of clickable navigation links without animation.
+ """
+ Returns a list of clickable navigation links without animation.
Args:
nav: The navigation object.
Returns:
List[str]: A list of clickable navigation links without animation.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ review_links = section_links(nav)
+ ```
"""
sections = nav.get_sections()
section_link = []
@@ -2353,26 +3375,134 @@ def section_links(nav) -> List[str]: # type: ignore
class Landlord(ALIndividual):
+ """
+ Landlord is a compatibility or role-specific subclass of ALIndividual.
+
+ Example:
+ This ALIndividual subclass uses the same name and address methods.
+ After gathering the list entry:
+
+ ```yaml
+ objects:
+ - landlords: DAList.using(object_type=Landlord)
+ ---
+ question: |
+ Check the name
+ subquestion: |
+ ${ landlords[0].name_full() }
+ ```
+ """
+
pass
class Tenant(ALIndividual):
+ """
+ Tenant is a compatibility or role-specific subclass of ALIndividual.
+
+ Example:
+ This ALIndividual subclass uses the same name and address methods.
+ After gathering the list entry:
+
+ ```yaml
+ objects:
+ - tenants: DAList.using(object_type=Tenant)
+ ---
+ question: |
+ Check the name
+ subquestion: |
+ ${ tenants[0].name_full() }
+ ```
+ """
+
pass
class HousingAuthority(Landlord):
+ """
+ HousingAuthority is a compatibility or role-specific subclass of Landlord.
+
+ Example:
+ This ALIndividual subclass uses the same name and address methods.
+ After gathering the list entry:
+
+ ```yaml
+ objects:
+ - housing_authorities: DAList.using(object_type=HousingAuthority)
+ ---
+ question: |
+ Check the name
+ subquestion: |
+ ${ housing_authorities[0].name_full() }
+ ```
+ """
+
pass
class Applicant(Tenant):
+ """
+ Applicant is a compatibility or role-specific subclass of Tenant.
+
+ Example:
+ This ALIndividual subclass uses the same name and address methods.
+ After gathering the list entry:
+
+ ```yaml
+ objects:
+ - applicants: DAList.using(object_type=Applicant)
+ ---
+ question: |
+ Check the name
+ subquestion: |
+ ${ applicants[0].name_full() }
+ ```
+ """
+
pass
class Abuser(ALIndividual):
+ """
+ Abuser is a compatibility or role-specific subclass of ALIndividual.
+
+ Example:
+ This ALIndividual subclass uses the same name and address methods.
+ After gathering the list entry:
+
+ ```yaml
+ objects:
+ - other_parties: DAList.using(object_type=Abuser)
+ ---
+ question: |
+ Check the name
+ subquestion: |
+ ${ other_parties[0].name_full() }
+ ```
+ """
+
pass
class Survivor(ALIndividual):
+ """
+ Survivor is a compatibility or role-specific subclass of ALIndividual.
+
+ Example:
+ This ALIndividual subclass uses the same name and address methods.
+ After gathering the list entry:
+
+ ```yaml
+ objects:
+ - survivors: ALPeopleList.using(object_type=Survivor)
+ ---
+ question: |
+ Check the name
+ subquestion: |
+ ${ survivors[0].name_full() }
+ ```
+ """
+
pass
@@ -2383,14 +3513,56 @@ class Survivor(ALIndividual):
class VCIndividual(ALIndividual):
+ """
+ VCIndividual is a compatibility or role-specific subclass of ALIndividual.
+
+ Example:
+ This ALIndividual subclass uses the same name and address methods.
+ After gathering the list entry:
+
+ ```yaml
+ objects:
+ - victims: ALPeopleList.using(object_type=VCIndividual)
+ ---
+ question: |
+ Check the name
+ subquestion: |
+ ${ victims[0].name_full() }
+ ```
+ """
+
pass
class AddressList(ALAddressList):
+ """
+ AddressList is a compatibility or role-specific subclass of ALAddressList.
+
+ Example:
+ Compatibility name for `ALAddressList`. Prefer `ALAddressList` in new interviews.
+
+ ```yaml
+ objects:
+ - previous_addresses: AddressList
+ ```
+ """
+
pass
class PeopleList(ALPeopleList):
+ """
+ PeopleList is a compatibility or role-specific subclass of ALPeopleList.
+
+ Example:
+ Compatibility name for `ALPeopleList`. Prefer `ALPeopleList` in new interviews.
+
+ ```yaml
+ objects:
+ - users: PeopleList
+ ```
+ """
+
pass
@@ -2409,6 +3581,14 @@ def will_send_to_real_court() -> bool:
Returns:
bool: True if the form is being run on the dev, test, or production server.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ send_to_court = will_send_to_real_court()
+ ```
"""
return not (
get_config("debug")
@@ -2421,7 +3601,8 @@ def will_send_to_real_court() -> bool:
# TODO: move to 209A package
# This one is only used for 209A--should move there along with the combined_letters() method
def filter_letters(letter_strings: Union[List[str], str]) -> str:
- """Used to take a list of letters like ["A","ABC","AB"] and filter out any duplicate letters.
+ """
+ Used to take a list of letters like ["A","ABC","AB"] and filter out any duplicate letters.
Avoid using, this is created for 209A.
@@ -2430,6 +3611,16 @@ def filter_letters(letter_strings: Union[List[str], str]) -> str:
Returns:
str: A string of unique letters.
+
+ Example:
+ Import `filter_letters` explicitly from
+ `docassemble.AssemblyLine.al_general` before using this example.
+ In an interview code block:
+
+ ```yaml
+ code: |
+ selected_letters = filter_letters(["ab", "bc"])
+ ```
"""
# There is probably a cute one liner, but this is easy to follow and
# probably same speed
@@ -2451,12 +3642,21 @@ def filter_letters(letter_strings: Union[List[str], str]) -> str:
def is_sms_enabled() -> bool:
- """Checks if SMS (Twilio) is enabled on the server. Does not verify that it works.
+ """
+ Checks if SMS (Twilio) is enabled on the server. Does not verify that it works.
See https://docassemble.org/docs/config.html#twilio for more info.
Returns:
bool: True if there is a non-empty Twilio config on the server, False otherwise.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ offer_text_message = is_sms_enabled()
+ ```
"""
twilio_config = get_config("twilio")
if isinstance(twilio_config, list):
@@ -2489,6 +3689,22 @@ def is_phone_or_email(text: str) -> bool:
Raises:
DAValidationError if the string is neither a valid phone number nor a valid email address.
+
+ Example:
+ The value of `valid_contact` for an email address:
+
+ **Input (interview YAML)**
+
+ ```yaml
+ code: |
+ valid_contact = is_phone_or_email("alex@example.com")
+ ```
+
+ **Output**
+
+ ```text
+ True
+ ```
"""
sms_enabled = is_sms_enabled()
if re.match(r"\S+@\S+", text) or (sms_enabled and phone_number_is_valid(text)):
@@ -2531,6 +3747,14 @@ def github_modified_date(
Returns:
Union[DADateTime, None]: The date that the given GitHub repository was modified or None if API call fails.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ last_updated = github_modified_date("SuffolkLITLab", "docassemble-AssemblyLine")
+ ```
"""
if not auth:
issue_config = get_config("github issues")
@@ -2555,7 +3779,8 @@ def github_modified_date(
# TODO(qs): remove if https://github.com/jhpyle/docassemble/pull/520 is merged
def language_name(language_code: str) -> str:
- """Given a 2 digit language code abbreviation, returns the full
+ """
+ Given a 2 digit language code abbreviation, returns the full
name of the language. The language name will be passed through the `word()`
function.
@@ -2564,6 +3789,27 @@ def language_name(language_code: str) -> str:
Returns:
str: The full name of the language.
+
+ Example:
+ With `users[0].language = "es"`:
+
+ **Input (Mako)**
+
+ ```mako
+ ${ language_name(users[0].language) }
+ ```
+
+ **Input (Jinja2)**
+
+ ```jinja2
+ {{ language_name(users[0].language) }}
+ ```
+
+ **Output**
+
+ ```text
+ Spanish
+ ```
"""
ensure_definition(language_code)
try:
@@ -2576,7 +3822,8 @@ def language_name(language_code: str) -> str:
def safe_states_list(country_code: str) -> List[Dict[str, str]]:
- """Wrapper around states_list that doesn't error if passed
+ """
+ Wrapper around states_list that doesn't error if passed
an invalid country_code (e.g., a country name spelled out)
Args:
@@ -2584,6 +3831,16 @@ def safe_states_list(country_code: str) -> List[Dict[str, str]]:
Returns:
List[Dict[str, str]]: A list of dictionaries with field prompts for states.
+
+ Example:
+ Import `safe_states_list` explicitly from
+ `docassemble.AssemblyLine.al_general` before using this example.
+ In an interview code block:
+
+ ```yaml
+ code: |
+ state_choices = safe_states_list("US")
+ ```
"""
try:
return states_list(country_code=country_code)
@@ -2600,6 +3857,22 @@ def has_parsable_pronouns(pronouns: str) -> bool:
Returns:
True if the pronouns string can be parsed into a dictionary of pronouns, False otherwise
+
+ Example:
+ The value of `valid_pronouns` for a custom pronoun string:
+
+ **Input (interview YAML)**
+
+ ```yaml
+ code: |
+ valid_pronouns = has_parsable_pronouns("them/they/their")
+ ```
+
+ **Output**
+
+ ```text
+ True
+ ```
"""
try:
parse_custom_pronouns(pronouns)
@@ -2617,6 +3890,23 @@ def parse_custom_pronouns(pronouns: str) -> Dict[str, str]:
Returns:
a dictionary of pronouns in the format {"o": objective, "s": subjective, "p": possessive}.
+
+ Example:
+ The value of `pronoun_parts` uses objective, subjective, and possessive
+ pronouns in that order:
+
+ **Input (interview YAML)**
+
+ ```yaml
+ code: |
+ pronoun_parts = parse_custom_pronouns("them/they/their")
+ ```
+
+ **Output**
+
+ ```text
+ {'o': 'them', 's': 'they', 'p': 'their'}
+ ```
"""
# test for presence of either 2 or 3 /'s
if not (2 <= pronouns.count("/") <= 3):
@@ -2652,6 +3942,19 @@ def get_visible_al_nav_items(
Returns:
a list of nav items with hidden items removed
+
+ Example:
+ Build navigation sections from interview answers in a code block:
+
+ ```yaml
+ code: |
+ al_nav_sections = [
+ {"about_you": "About you"},
+ {"about_children": "Children", "hidden": not has_children},
+ {"review": "Review your answers"},
+ ]
+ nav.set_sections(get_visible_al_nav_items(al_nav_sections))
+ ```
"""
new_list: List[Union[str, dict]] = []
diff --git a/docassemble/AssemblyLine/custom_jinja_filters.py b/docassemble/AssemblyLine/custom_jinja_filters.py
index 65f1a215..433bd36f 100644
--- a/docassemble/AssemblyLine/custom_jinja_filters.py
+++ b/docassemble/AssemblyLine/custom_jinja_filters.py
@@ -40,15 +40,15 @@ def catchall_options(value: Any, *raw_items: Any) -> DACatchAll:
Example usage in a DOCX template:
- ```
- {{ my_catchall_field | catchall_options("code1: label1", "code2: label2") }}
+ ```jinja2
+ {{ users[0].preferred_contact | catchall_options("email: Email", "phone: Phone") }}
- {{ my_catchall_field_2 | catchall_options({"code1": "label1"}, {"code2": "label2"}) }}
+ {{ users[0].preferred_contact | catchall_options({"email": "Email"}, {"phone": "Phone"}) }}
```
Example in an interview with `features: use catchall: True` turned on:
- ```
+ ```yaml
---
if: |
hasattr(x, "_catchall_options")
@@ -107,11 +107,11 @@ def catchall_label(value: Any, label: str) -> DACatchAll:
catchall field in the user interface.
Example usage in a DOCX template:
- ```
- {{ my_catchall_field | catchall_label("My Custom Label") }}
+ ```jinja2
+ {{ users[0].preferred_contact | catchall_label("Preferred contact method") }}
```
Example in an interview with `features: use catchall: True` turned on:
- ```
+ ```yaml
---
generic object: DACatchAll
question: |
@@ -140,8 +140,8 @@ def catchall_datatype(value: Any, datatype: str) -> DACatchAll:
catchall field.
Example usage in a DOCX template:
- ```
- {{ my_catchall_field | catchall_datatype("radio") }}
+ ```jinja2
+ {{ users[0].monthly_income | catchall_datatype("currency") }}
```
Args:
@@ -164,8 +164,8 @@ def catchall_question(value: Any, question: str) -> DACatchAll:
related to the catchall field.
Example usage in a DOCX template:
- ```
- {{ my_catchall_field | catchall_question("What additional information do you need?") }}
+ ```jinja2
+ {{ users[0].preferred_contact | catchall_question("How would you like to be contacted?") }}
```
Args:
@@ -188,8 +188,8 @@ def catchall_subquestion(value: Any, subquestion: str) -> DACatchAll:
related to the catchall field.
Example usage in a DOCX template:
- ```
- {{ my_catchall_field | catchall_subquestion("Please provide additional details.") }}
+ ```jinja2
+ {{ users[0].monthly_income | catchall_subquestion("Enter your income before taxes for a typical month.") }}
```
Args:
@@ -218,8 +218,8 @@ def catchall_complete(
Each argument after `value` corresponds to the similarly named `catchall_` function.
Example usage in a DOCX template:
- ```
- {{ my_catchall_field | catchall_complete(question="What additional information do you need?", subquestion="Be specific", label="no label") }}
+ ```jinja2
+ {{ users[0].monthly_income | catchall_complete(question="What is your monthly income?", subquestion="Enter the amount before taxes.", label="Monthly income", datatype="currency") }}
```
Args:
@@ -287,42 +287,42 @@ def if_final(
Example:
Contents of test_if_final.docx:
- ```jinja
- {{ users[0].signature | if_final }}
- ```
+ ```jinja
+ {{ users[0].signature | if_final }}
+ ```
Returns "[ signature ]" if `i` (passed to the context of the attachment block) is not "final",
otherwise the actual value of `users[0].signature`.
- ```yaml
- ---
- include:
- - assembly_line.yml
- ---
- mandatory: True
- code: |
- preview_screen
- final_screen
- ---
- question: |
- Here is what it looks like unsigned
- subquestion: |
- ${ test_if_final_attachment.as_pdf(key="preview") }
- continue button field: preview_screen
- ---
- question: |
- Here is what it looks like signed
- subquestion: |
- ${ test_if_final_attachment.as_pdf(key="final") }
- event: final_screen
- ---
- objects:
- - test_if_final_attachment: ALDocument.using(title="test_if_final", filename="test_if_final")
- ---
- attachment:
- variable name: test_if_final_attachment[i]
- docx template file: test_if_final.docx
- ```
+ ```yaml
+ ---
+ include:
+ - assembly_line.yml
+ ---
+ mandatory: True
+ code: |
+ preview_screen
+ final_screen
+ ---
+ question: |
+ Here is what it looks like unsigned
+ subquestion: |
+ ${ test_if_final_attachment.as_pdf(key="preview") }
+ continue button field: preview_screen
+ ---
+ question: |
+ Here is what it looks like signed
+ subquestion: |
+ ${ test_if_final_attachment.as_pdf(key="final") }
+ event: final_screen
+ ---
+ objects:
+ - test_if_final_attachment: ALDocument.using(title="test_if_final", filename="test_if_final")
+ ---
+ attachment:
+ variable name: test_if_final_attachment[i]
+ docx template file: test_if_final.docx
+ ```
Args:
context (Jinja2Context): The Jinja2 context, automatically passed by the `pass_context` decorator.
@@ -369,7 +369,23 @@ def catchall_fields_code(value: Any) -> List[Dict[str, Any]]:
Args:
value (DACatchAll): The DACatchAll object containing the question and options.
Returns:
- List[Dict[str, Any]]: A dictionary containing the Docassemble code for the catchall question.
+ List[Dict[str, Any]]: A list with one field definition for the catchall question, or an empty list
+ if `value` is not a DACatchAll.
+
+ Example:
+ In an interview with `features: use catchall: True`, build fields for
+ the undefined value represented by `x`. Docassemble cannot tell which
+ variable fields generated by `code` will define, so `sets` is required
+ for this question to be found:
+
+ ```yaml
+ generic object: DACatchAll
+ sets: x.value
+ question: |
+ Please enter the missing information
+ fields:
+ - code: catchall_fields_code(x)
+ ```
"""
if isinstance(value, DACatchAll):
choices = value._catchall_options if hasattr(value, "_catchall_options") else []
diff --git a/docassemble/AssemblyLine/data/questions/interview_list.yml b/docassemble/AssemblyLine/data/questions/interview_list.yml
index 96331ef5..7e22ef40 100644
--- a/docassemble/AssemblyLine/data/questions/interview_list.yml
+++ b/docassemble/AssemblyLine/data/questions/interview_list.yml
@@ -376,6 +376,9 @@ fields:
continue button label: |
:save: Save copy
---
+event: interview_list_copy_action
+# Gather the name before Docassemble consumes this event from the action queue.
+need: al_sessions_copy_as_answer_set_label
code: |
save_interview_answers(
source_filename=action_argument("filename"),
@@ -384,7 +387,6 @@ code: |
original_interview_filename=action_argument("original_interview_filename"),
)
log("Copied answers", "success")
- interview_list_copy_action = True
---
id: rename answer
question: |
diff --git a/docassemble/AssemblyLine/language.py b/docassemble/AssemblyLine/language.py
index 2f0813ce..ce7bbd30 100644
--- a/docassemble/AssemblyLine/language.py
+++ b/docassemble/AssemblyLine/language.py
@@ -37,6 +37,15 @@ def get_local_languages_yaml() -> str:
Returns:
str: the path to the local languages.yml file if it exists, otherwise the path to the languages.yml file.
+
+ Example:
+ In an interview code block:
+
+ ```yaml
+ code: |
+ from docassemble.AssemblyLine.language import get_local_languages_yaml
+ languages_path = get_local_languages_yaml()
+ ```
"""
try:
local_yaml = path_and_mimetype("data/sources/languages.yml")[0]
@@ -67,6 +76,22 @@ def get_tuples(
Returns:
A list of tuples representing the language name, followed by language ISO 639-1 code.
+ Example:
+ With the standard AssemblyLine `languages.yml`, the value of
+ `language_choices` contains each language’s native name and code:
+
+ **Input (interview YAML)**
+
+ ```yaml
+ code: |
+ language_choices = get_tuples(["en", "es"])
+ ```
+
+ **Output**
+
+ ```text
+ [('English', 'en'), ('Español', 'es')]
+ ```
"""
if not languages_path:
languages_path = get_local_languages_yaml()
@@ -120,6 +145,13 @@ def get_language_list_dropdown(
extra_class: additional classes to add to the link.
Returns:
A string containing the HTML for a dropdown menu for language selection.
+
+ Example:
+ In question text (Mako):
+
+ ```mako
+ ${ get_language_list_dropdown(lang_codes=["en", "es"], current=get_language()) }
+ ```
"""
list_start = f"""