Files
darth-veitcher-comfydv/0.1.1.dev23+g9ca65bc29/reference/comfydv/format_string/index.html
T

4660 lines
240 KiB
HTML

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="prev" href="../circuit_breaker/">
<link rel="next" href="../ollama/">
<link rel="icon" href="../../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.9">
<title>format_string - ComfyPPT</title>
<link rel="stylesheet" href="../../../assets/stylesheets/main.4af4bdda.min.css">
<link rel="stylesheet" href="../../../assets/stylesheets/palette.06af60db.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<link rel="stylesheet" href="../../../assets/_mkdocstrings.css">
<script>__md_scope=new URL("../../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="deep-purple" data-md-color-accent="indigo">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#comfydv.format_string" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../../.." title="ComfyPPT" class="md-header__button md-logo" aria-label="ComfyPPT" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
ComfyPPT
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
format_string
</span>
</div>
</div>
</div>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../../.." title="ComfyPPT" class="md-nav__button md-logo" aria-label="ComfyPPT" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
ComfyPPT
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" checked>
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
<span class="md-ellipsis">
Code Reference
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
Code Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2_1" checked>
<div class="md-nav__link md-nav__container">
<a href="../" class="md-nav__link ">
<span class="md-ellipsis">
comfydv
</span>
</a>
<label class="md-nav__link " for="__nav_2_1" id="__nav_2_1_label" tabindex="0">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_1_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2_1">
<span class="md-nav__icon md-icon"></span>
comfydv
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2_1_1" >
<div class="md-nav__link md-nav__container">
<a href="../_llm/" class="md-nav__link ">
<span class="md-ellipsis">
_llm
</span>
</a>
<label class="md-nav__link " for="__nav_2_1_1" id="__nav_2_1_1_label" tabindex="0">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="3" aria-labelledby="__nav_2_1_1_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2_1_1">
<span class="md-nav__icon md-icon"></span>
_llm
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../_llm/chat/" class="md-nav__link">
<span class="md-ellipsis">
chat
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../_llm/ollama_provider/" class="md-nav__link">
<span class="md-ellipsis">
ollama_provider
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../_llm/provider/" class="md-nav__link">
<span class="md-ellipsis">
provider
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../circuit_breaker/" class="md-nav__link">
<span class="md-ellipsis">
circuit_breaker
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
format_string
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
format_string
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#comfydv.format_string" class="md-nav__link">
<span class="md-ellipsis">
format_string
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString" class="md-nav__link">
<span class="md-ellipsis">
FormatString
</span>
</a>
<nav class="md-nav" aria-label="FormatString">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.INPUT_TYPES" class="md-nav__link">
<span class="md-ellipsis">
INPUT_TYPES
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.IS_CHANGED" class="md-nav__link">
<span class="md-ellipsis">
IS_CHANGED
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.format_string" class="md-nav__link">
<span class="md-ellipsis">
format_string
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.get_node_config" class="md-nav__link">
<span class="md-ellipsis">
get_node_config
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.load_node_state" class="md-nav__link">
<span class="md-ellipsis">
load_node_state
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.time_now" class="md-nav__link">
<span class="md-ellipsis">
time_now
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.update_widget" class="md-nav__link">
<span class="md-ellipsis">
update_widget
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.get_format_string_node_config" class="md-nav__link">
<span class="md-ellipsis">
get_format_string_node_config
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.load_format_string_node" class="md-nav__link">
<span class="md-ellipsis">
load_format_string_node
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.update_format_string_node" class="md-nav__link">
<span class="md-ellipsis">
update_format_string_node
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../ollama/" class="md-nav__link">
<span class="md-ellipsis">
ollama
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../random_choice/" class="md-nav__link">
<span class="md-ellipsis">
random_choice
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../utils/" class="md-nav__link">
<span class="md-ellipsis">
utils
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#comfydv.format_string" class="md-nav__link">
<span class="md-ellipsis">
format_string
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString" class="md-nav__link">
<span class="md-ellipsis">
FormatString
</span>
</a>
<nav class="md-nav" aria-label="FormatString">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.INPUT_TYPES" class="md-nav__link">
<span class="md-ellipsis">
INPUT_TYPES
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.IS_CHANGED" class="md-nav__link">
<span class="md-ellipsis">
IS_CHANGED
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.format_string" class="md-nav__link">
<span class="md-ellipsis">
format_string
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.get_node_config" class="md-nav__link">
<span class="md-ellipsis">
get_node_config
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.load_node_state" class="md-nav__link">
<span class="md-ellipsis">
load_node_state
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.time_now" class="md-nav__link">
<span class="md-ellipsis">
time_now
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.FormatString.update_widget" class="md-nav__link">
<span class="md-ellipsis">
update_widget
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.get_format_string_node_config" class="md-nav__link">
<span class="md-ellipsis">
get_format_string_node_config
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.load_format_string_node" class="md-nav__link">
<span class="md-ellipsis">
load_format_string_node
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#comfydv.format_string.update_format_string_node" class="md-nav__link">
<span class="md-ellipsis">
update_format_string_node
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1>format_string</h1>
<div class="doc doc-object doc-module">
<a id="comfydv.format_string"></a>
<div class="doc doc-contents first">
<p>FormatString module for ComfyUI.</p>
<p>This module provides a custom node for ComfyUI that allows formatting strings
using either simple Python formatting or Jinja2 templates. It can also save
formatted templates to disk for later reuse.</p>
<p>The node dynamically updates its inputs and outputs based on the variables
detected in the template, making it highly flexible for various text generation
and parameter formatting needs in ComfyUI workflows.</p>
<div class="doc doc-children">
<div class="doc doc-object doc-class">
<h2 id="comfydv.format_string.FormatString" class="doc doc-heading">
<code>FormatString</code>
</h2>
<div class="doc doc-contents ">
<p>A ComfyUI node for string formatting using Python's format syntax or Jinja2 templates.</p>
<p>This node dynamically adapts its inputs and outputs based on the variables detected
in the provided template. It supports saving template state to disk and loading it back.
The node can operate in two modes:
1. Simple: Uses Python's str.format() method
2. Jinja2: Uses Jinja2 templating engine with sandbox protection</p>
<p>Additional context variables like datetime, random, and math functions are available
in Jinja2 mode.</p>
<details class="outputs" open>
<summary>Outputs</summary>
<p>The node always returns formatted_string and saved_file_path as the first two outputs
(positions 0 and 1), followed by any variable values extracted from the template in
subsequent positions. This ensures the primary outputs are in fixed, predictable positions.</p>
</details>
<p><span class="doc-section-title">Attributes:</span></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td><code><span title="comfydv.format_string.FormatString.CATEGORY">CATEGORY</span></code></td>
<td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>The category of the node in ComfyUI's node menu.</p>
</div>
</td>
</tr>
<tr class="doc-section-item">
<td><code><span title="comfydv.format_string.FormatString.FUNCTION">FUNCTION</span></code></td>
<td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>The main function to be called when the node is executed.</p>
</div>
</td>
</tr>
<tr class="doc-section-item">
<td><code><span title="comfydv.format_string.FormatString.RETURN_TYPES">RETURN_TYPES</span></code></td>
<td>
<code><span title="tuple">tuple</span></code>
</td>
<td>
<div class="doc-md-description">
<p>Types of the returned outputs (dynamically updated).</p>
</div>
</td>
</tr>
<tr class="doc-section-item">
<td><code><span title="comfydv.format_string.FormatString.RETURN_NAMES">RETURN_NAMES</span></code></td>
<td>
<code><span title="tuple">tuple</span></code>
</td>
<td>
<div class="doc-md-description">
<p>Names of the returned outputs (dynamically updated).</p>
</div>
</td>
</tr>
<tr class="doc-section-item">
<td><code><span title="comfydv.format_string.FormatString.node_configs">node_configs</span></code></td>
<td>
<code><span title="dict">dict</span></code>
</td>
<td>
<div class="doc-md-description">
<p>Storage for configurations of node instances.</p>
</div>
</td>
</tr>
<tr class="doc-section-item">
<td><code><span title="comfydv.format_string.FormatString.jinja_env">jinja_env</span></code></td>
<td>
<code><span title="SandboxedEnvironment">SandboxedEnvironment</span></code>
</td>
<td>
<div class="doc-md-description">
<p>Sandboxed Jinja2 environment for secure template rendering.</p>
</div>
</td>
</tr>
<tr class="doc-section-item">
<td><code><span title="comfydv.format_string.FormatString.additional_context">additional_context</span></code></td>
<td>
<code><span title="dict">dict</span></code>
</td>
<td>
<div class="doc-md-description">
<p>Extra context variables available in Jinja2 templates.</p>
</div>
</td>
</tr>
</tbody>
</table>
<details class="quote">
<summary>Source code in <code>src/comfydv/format_string.py</code></summary>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"> 38</span>
<span class="normal"> 39</span>
<span class="normal"> 40</span>
<span class="normal"> 41</span>
<span class="normal"> 42</span>
<span class="normal"> 43</span>
<span class="normal"> 44</span>
<span class="normal"> 45</span>
<span class="normal"> 46</span>
<span class="normal"> 47</span>
<span class="normal"> 48</span>
<span class="normal"> 49</span>
<span class="normal"> 50</span>
<span class="normal"> 51</span>
<span class="normal"> 52</span>
<span class="normal"> 53</span>
<span class="normal"> 54</span>
<span class="normal"> 55</span>
<span class="normal"> 56</span>
<span class="normal"> 57</span>
<span class="normal"> 58</span>
<span class="normal"> 59</span>
<span class="normal"> 60</span>
<span class="normal"> 61</span>
<span class="normal"> 62</span>
<span class="normal"> 63</span>
<span class="normal"> 64</span>
<span class="normal"> 65</span>
<span class="normal"> 66</span>
<span class="normal"> 67</span>
<span class="normal"> 68</span>
<span class="normal"> 69</span>
<span class="normal"> 70</span>
<span class="normal"> 71</span>
<span class="normal"> 72</span>
<span class="normal"> 73</span>
<span class="normal"> 74</span>
<span class="normal"> 75</span>
<span class="normal"> 76</span>
<span class="normal"> 77</span>
<span class="normal"> 78</span>
<span class="normal"> 79</span>
<span class="normal"> 80</span>
<span class="normal"> 81</span>
<span class="normal"> 82</span>
<span class="normal"> 83</span>
<span class="normal"> 84</span>
<span class="normal"> 85</span>
<span class="normal"> 86</span>
<span class="normal"> 87</span>
<span class="normal"> 88</span>
<span class="normal"> 89</span>
<span class="normal"> 90</span>
<span class="normal"> 91</span>
<span class="normal"> 92</span>
<span class="normal"> 93</span>
<span class="normal"> 94</span>
<span class="normal"> 95</span>
<span class="normal"> 96</span>
<span class="normal"> 97</span>
<span class="normal"> 98</span>
<span class="normal"> 99</span>
<span class="normal">100</span>
<span class="normal">101</span>
<span class="normal">102</span>
<span class="normal">103</span>
<span class="normal">104</span>
<span class="normal">105</span>
<span class="normal">106</span>
<span class="normal">107</span>
<span class="normal">108</span>
<span class="normal">109</span>
<span class="normal">110</span>
<span class="normal">111</span>
<span class="normal">112</span>
<span class="normal">113</span>
<span class="normal">114</span>
<span class="normal">115</span>
<span class="normal">116</span>
<span class="normal">117</span>
<span class="normal">118</span>
<span class="normal">119</span>
<span class="normal">120</span>
<span class="normal">121</span>
<span class="normal">122</span>
<span class="normal">123</span>
<span class="normal">124</span>
<span class="normal">125</span>
<span class="normal">126</span>
<span class="normal">127</span>
<span class="normal">128</span>
<span class="normal">129</span>
<span class="normal">130</span>
<span class="normal">131</span>
<span class="normal">132</span>
<span class="normal">133</span>
<span class="normal">134</span>
<span class="normal">135</span>
<span class="normal">136</span>
<span class="normal">137</span>
<span class="normal">138</span>
<span class="normal">139</span>
<span class="normal">140</span>
<span class="normal">141</span>
<span class="normal">142</span>
<span class="normal">143</span>
<span class="normal">144</span>
<span class="normal">145</span>
<span class="normal">146</span>
<span class="normal">147</span>
<span class="normal">148</span>
<span class="normal">149</span>
<span class="normal">150</span>
<span class="normal">151</span>
<span class="normal">152</span>
<span class="normal">153</span>
<span class="normal">154</span>
<span class="normal">155</span>
<span class="normal">156</span>
<span class="normal">157</span>
<span class="normal">158</span>
<span class="normal">159</span>
<span class="normal">160</span>
<span class="normal">161</span>
<span class="normal">162</span>
<span class="normal">163</span>
<span class="normal">164</span>
<span class="normal">165</span>
<span class="normal">166</span>
<span class="normal">167</span>
<span class="normal">168</span>
<span class="normal">169</span>
<span class="normal">170</span>
<span class="normal">171</span>
<span class="normal">172</span>
<span class="normal">173</span>
<span class="normal">174</span>
<span class="normal">175</span>
<span class="normal">176</span>
<span class="normal">177</span>
<span class="normal">178</span>
<span class="normal">179</span>
<span class="normal">180</span>
<span class="normal">181</span>
<span class="normal">182</span>
<span class="normal">183</span>
<span class="normal">184</span>
<span class="normal">185</span>
<span class="normal">186</span>
<span class="normal">187</span>
<span class="normal">188</span>
<span class="normal">189</span>
<span class="normal">190</span>
<span class="normal">191</span>
<span class="normal">192</span>
<span class="normal">193</span>
<span class="normal">194</span>
<span class="normal">195</span>
<span class="normal">196</span>
<span class="normal">197</span>
<span class="normal">198</span>
<span class="normal">199</span>
<span class="normal">200</span>
<span class="normal">201</span>
<span class="normal">202</span>
<span class="normal">203</span>
<span class="normal">204</span>
<span class="normal">205</span>
<span class="normal">206</span>
<span class="normal">207</span>
<span class="normal">208</span>
<span class="normal">209</span>
<span class="normal">210</span>
<span class="normal">211</span>
<span class="normal">212</span>
<span class="normal">213</span>
<span class="normal">214</span>
<span class="normal">215</span>
<span class="normal">216</span>
<span class="normal">217</span>
<span class="normal">218</span>
<span class="normal">219</span>
<span class="normal">220</span>
<span class="normal">221</span>
<span class="normal">222</span>
<span class="normal">223</span>
<span class="normal">224</span>
<span class="normal">225</span>
<span class="normal">226</span>
<span class="normal">227</span>
<span class="normal">228</span>
<span class="normal">229</span>
<span class="normal">230</span>
<span class="normal">231</span>
<span class="normal">232</span>
<span class="normal">233</span>
<span class="normal">234</span>
<span class="normal">235</span>
<span class="normal">236</span>
<span class="normal">237</span>
<span class="normal">238</span>
<span class="normal">239</span>
<span class="normal">240</span>
<span class="normal">241</span>
<span class="normal">242</span>
<span class="normal">243</span>
<span class="normal">244</span>
<span class="normal">245</span>
<span class="normal">246</span>
<span class="normal">247</span>
<span class="normal">248</span>
<span class="normal">249</span>
<span class="normal">250</span>
<span class="normal">251</span>
<span class="normal">252</span>
<span class="normal">253</span>
<span class="normal">254</span>
<span class="normal">255</span>
<span class="normal">256</span>
<span class="normal">257</span>
<span class="normal">258</span>
<span class="normal">259</span>
<span class="normal">260</span>
<span class="normal">261</span>
<span class="normal">262</span>
<span class="normal">263</span>
<span class="normal">264</span>
<span class="normal">265</span>
<span class="normal">266</span>
<span class="normal">267</span>
<span class="normal">268</span>
<span class="normal">269</span>
<span class="normal">270</span>
<span class="normal">271</span>
<span class="normal">272</span>
<span class="normal">273</span>
<span class="normal">274</span>
<span class="normal">275</span>
<span class="normal">276</span>
<span class="normal">277</span>
<span class="normal">278</span>
<span class="normal">279</span>
<span class="normal">280</span>
<span class="normal">281</span>
<span class="normal">282</span>
<span class="normal">283</span>
<span class="normal">284</span>
<span class="normal">285</span>
<span class="normal">286</span>
<span class="normal">287</span>
<span class="normal">288</span>
<span class="normal">289</span>
<span class="normal">290</span>
<span class="normal">291</span>
<span class="normal">292</span>
<span class="normal">293</span>
<span class="normal">294</span>
<span class="normal">295</span>
<span class="normal">296</span>
<span class="normal">297</span>
<span class="normal">298</span>
<span class="normal">299</span>
<span class="normal">300</span>
<span class="normal">301</span>
<span class="normal">302</span>
<span class="normal">303</span>
<span class="normal">304</span>
<span class="normal">305</span>
<span class="normal">306</span>
<span class="normal">307</span>
<span class="normal">308</span>
<span class="normal">309</span>
<span class="normal">310</span>
<span class="normal">311</span>
<span class="normal">312</span>
<span class="normal">313</span>
<span class="normal">314</span>
<span class="normal">315</span>
<span class="normal">316</span>
<span class="normal">317</span>
<span class="normal">318</span>
<span class="normal">319</span>
<span class="normal">320</span>
<span class="normal">321</span>
<span class="normal">322</span>
<span class="normal">323</span>
<span class="normal">324</span>
<span class="normal">325</span>
<span class="normal">326</span>
<span class="normal">327</span>
<span class="normal">328</span>
<span class="normal">329</span>
<span class="normal">330</span>
<span class="normal">331</span>
<span class="normal">332</span>
<span class="normal">333</span>
<span class="normal">334</span>
<span class="normal">335</span>
<span class="normal">336</span>
<span class="normal">337</span>
<span class="normal">338</span>
<span class="normal">339</span>
<span class="normal">340</span>
<span class="normal">341</span>
<span class="normal">342</span>
<span class="normal">343</span>
<span class="normal">344</span>
<span class="normal">345</span>
<span class="normal">346</span>
<span class="normal">347</span>
<span class="normal">348</span>
<span class="normal">349</span>
<span class="normal">350</span>
<span class="normal">351</span>
<span class="normal">352</span>
<span class="normal">353</span>
<span class="normal">354</span>
<span class="normal">355</span>
<span class="normal">356</span>
<span class="normal">357</span>
<span class="normal">358</span>
<span class="normal">359</span>
<span class="normal">360</span>
<span class="normal">361</span>
<span class="normal">362</span>
<span class="normal">363</span>
<span class="normal">364</span>
<span class="normal">365</span>
<span class="normal">366</span>
<span class="normal">367</span>
<span class="normal">368</span>
<span class="normal">369</span>
<span class="normal">370</span>
<span class="normal">371</span>
<span class="normal">372</span>
<span class="normal">373</span>
<span class="normal">374</span>
<span class="normal">375</span>
<span class="normal">376</span>
<span class="normal">377</span>
<span class="normal">378</span>
<span class="normal">379</span>
<span class="normal">380</span>
<span class="normal">381</span>
<span class="normal">382</span>
<span class="normal">383</span>
<span class="normal">384</span>
<span class="normal">385</span>
<span class="normal">386</span>
<span class="normal">387</span>
<span class="normal">388</span>
<span class="normal">389</span>
<span class="normal">390</span>
<span class="normal">391</span>
<span class="normal">392</span>
<span class="normal">393</span>
<span class="normal">394</span>
<span class="normal">395</span>
<span class="normal">396</span>
<span class="normal">397</span>
<span class="normal">398</span>
<span class="normal">399</span>
<span class="normal">400</span>
<span class="normal">401</span>
<span class="normal">402</span>
<span class="normal">403</span>
<span class="normal">404</span>
<span class="normal">405</span>
<span class="normal">406</span>
<span class="normal">407</span>
<span class="normal">408</span>
<span class="normal">409</span>
<span class="normal">410</span>
<span class="normal">411</span>
<span class="normal">412</span>
<span class="normal">413</span>
<span class="normal">414</span>
<span class="normal">415</span>
<span class="normal">416</span>
<span class="normal">417</span>
<span class="normal">418</span>
<span class="normal">419</span>
<span class="normal">420</span>
<span class="normal">421</span>
<span class="normal">422</span>
<span class="normal">423</span>
<span class="normal">424</span>
<span class="normal">425</span>
<span class="normal">426</span>
<span class="normal">427</span>
<span class="normal">428</span>
<span class="normal">429</span>
<span class="normal">430</span>
<span class="normal">431</span>
<span class="normal">432</span>
<span class="normal">433</span>
<span class="normal">434</span>
<span class="normal">435</span>
<span class="normal">436</span>
<span class="normal">437</span>
<span class="normal">438</span>
<span class="normal">439</span>
<span class="normal">440</span>
<span class="normal">441</span>
<span class="normal">442</span>
<span class="normal">443</span>
<span class="normal">444</span>
<span class="normal">445</span>
<span class="normal">446</span>
<span class="normal">447</span>
<span class="normal">448</span>
<span class="normal">449</span>
<span class="normal">450</span>
<span class="normal">451</span>
<span class="normal">452</span>
<span class="normal">453</span>
<span class="normal">454</span>
<span class="normal">455</span>
<span class="normal">456</span>
<span class="normal">457</span>
<span class="normal">458</span>
<span class="normal">459</span>
<span class="normal">460</span>
<span class="normal">461</span>
<span class="normal">462</span>
<span class="normal">463</span>
<span class="normal">464</span>
<span class="normal">465</span>
<span class="normal">466</span>
<span class="normal">467</span>
<span class="normal">468</span>
<span class="normal">469</span>
<span class="normal">470</span>
<span class="normal">471</span>
<span class="normal">472</span>
<span class="normal">473</span>
<span class="normal">474</span>
<span class="normal">475</span>
<span class="normal">476</span>
<span class="normal">477</span>
<span class="normal">478</span>
<span class="normal">479</span>
<span class="normal">480</span>
<span class="normal">481</span>
<span class="normal">482</span>
<span class="normal">483</span>
<span class="normal">484</span>
<span class="normal">485</span>
<span class="normal">486</span>
<span class="normal">487</span>
<span class="normal">488</span>
<span class="normal">489</span>
<span class="normal">490</span>
<span class="normal">491</span>
<span class="normal">492</span>
<span class="normal">493</span>
<span class="normal">494</span>
<span class="normal">495</span>
<span class="normal">496</span>
<span class="normal">497</span>
<span class="normal">498</span>
<span class="normal">499</span>
<span class="normal">500</span>
<span class="normal">501</span>
<span class="normal">502</span>
<span class="normal">503</span>
<span class="normal">504</span>
<span class="normal">505</span>
<span class="normal">506</span>
<span class="normal">507</span>
<span class="normal">508</span>
<span class="normal">509</span>
<span class="normal">510</span>
<span class="normal">511</span>
<span class="normal">512</span>
<span class="normal">513</span>
<span class="normal">514</span>
<span class="normal">515</span>
<span class="normal">516</span>
<span class="normal">517</span>
<span class="normal">518</span>
<span class="normal">519</span>
<span class="normal">520</span>
<span class="normal">521</span>
<span class="normal">522</span>
<span class="normal">523</span>
<span class="normal">524</span>
<span class="normal">525</span>
<span class="normal">526</span>
<span class="normal">527</span>
<span class="normal">528</span>
<span class="normal">529</span>
<span class="normal">530</span>
<span class="normal">531</span>
<span class="normal">532</span>
<span class="normal">533</span>
<span class="normal">534</span>
<span class="normal">535</span>
<span class="normal">536</span>
<span class="normal">537</span>
<span class="normal">538</span>
<span class="normal">539</span>
<span class="normal">540</span>
<span class="normal">541</span>
<span class="normal">542</span>
<span class="normal">543</span>
<span class="normal">544</span>
<span class="normal">545</span>
<span class="normal">546</span>
<span class="normal">547</span>
<span class="normal">548</span>
<span class="normal">549</span>
<span class="normal">550</span>
<span class="normal">551</span>
<span class="normal">552</span>
<span class="normal">553</span>
<span class="normal">554</span>
<span class="normal">555</span>
<span class="normal">556</span>
<span class="normal">557</span>
<span class="normal">558</span>
<span class="normal">559</span>
<span class="normal">560</span>
<span class="normal">561</span>
<span class="normal">562</span>
<span class="normal">563</span>
<span class="normal">564</span>
<span class="normal">565</span>
<span class="normal">566</span>
<span class="normal">567</span>
<span class="normal">568</span>
<span class="normal">569</span>
<span class="normal">570</span>
<span class="normal">571</span>
<span class="normal">572</span>
<span class="normal">573</span>
<span class="normal">574</span>
<span class="normal">575</span>
<span class="normal">576</span>
<span class="normal">577</span>
<span class="normal">578</span>
<span class="normal">579</span>
<span class="normal">580</span>
<span class="normal">581</span>
<span class="normal">582</span>
<span class="normal">583</span>
<span class="normal">584</span>
<span class="normal">585</span>
<span class="normal">586</span>
<span class="normal">587</span>
<span class="normal">588</span>
<span class="normal">589</span>
<span class="normal">590</span>
<span class="normal">591</span>
<span class="normal">592</span>
<span class="normal">593</span>
<span class="normal">594</span>
<span class="normal">595</span>
<span class="normal">596</span>
<span class="normal">597</span>
<span class="normal">598</span>
<span class="normal">599</span>
<span class="normal">600</span>
<span class="normal">601</span>
<span class="normal">602</span>
<span class="normal">603</span>
<span class="normal">604</span>
<span class="normal">605</span>
<span class="normal">606</span>
<span class="normal">607</span>
<span class="normal">608</span>
<span class="normal">609</span>
<span class="normal">610</span>
<span class="normal">611</span>
<span class="normal">612</span>
<span class="normal">613</span>
<span class="normal">614</span>
<span class="normal">615</span>
<span class="normal">616</span>
<span class="normal">617</span>
<span class="normal">618</span>
<span class="normal">619</span>
<span class="normal">620</span>
<span class="normal">621</span>
<span class="normal">622</span>
<span class="normal">623</span>
<span class="normal">624</span>
<span class="normal">625</span>
<span class="normal">626</span>
<span class="normal">627</span>
<span class="normal">628</span>
<span class="normal">629</span>
<span class="normal">630</span>
<span class="normal">631</span>
<span class="normal">632</span>
<span class="normal">633</span>
<span class="normal">634</span>
<span class="normal">635</span>
<span class="normal">636</span>
<span class="normal">637</span>
<span class="normal">638</span>
<span class="normal">639</span>
<span class="normal">640</span>
<span class="normal">641</span>
<span class="normal">642</span>
<span class="normal">643</span>
<span class="normal">644</span>
<span class="normal">645</span>
<span class="normal">646</span>
<span class="normal">647</span>
<span class="normal">648</span>
<span class="normal">649</span>
<span class="normal">650</span>
<span class="normal">651</span>
<span class="normal">652</span>
<span class="normal">653</span>
<span class="normal">654</span>
<span class="normal">655</span>
<span class="normal">656</span>
<span class="normal">657</span>
<span class="normal">658</span>
<span class="normal">659</span>
<span class="normal">660</span>
<span class="normal">661</span>
<span class="normal">662</span>
<span class="normal">663</span>
<span class="normal">664</span>
<span class="normal">665</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">FormatString</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> A ComfyUI node for string formatting using Python&#39;s format syntax or Jinja2 templates.</span>
<span class="sd"> This node dynamically adapts its inputs and outputs based on the variables detected</span>
<span class="sd"> in the provided template. It supports saving template state to disk and loading it back.</span>
<span class="sd"> The node can operate in two modes:</span>
<span class="sd"> 1. Simple: Uses Python&#39;s str.format() method</span>
<span class="sd"> 2. Jinja2: Uses Jinja2 templating engine with sandbox protection</span>
<span class="sd"> Additional context variables like datetime, random, and math functions are available</span>
<span class="sd"> in Jinja2 mode.</span>
<span class="sd"> Outputs:</span>
<span class="sd"> The node always returns formatted_string and saved_file_path as the first two outputs</span>
<span class="sd"> (positions 0 and 1), followed by any variable values extracted from the template in</span>
<span class="sd"> subsequent positions. This ensures the primary outputs are in fixed, predictable positions.</span>
<span class="sd"> Attributes:</span>
<span class="sd"> CATEGORY (str): The category of the node in ComfyUI&#39;s node menu.</span>
<span class="sd"> FUNCTION (str): The main function to be called when the node is executed.</span>
<span class="sd"> RETURN_TYPES (tuple): Types of the returned outputs (dynamically updated).</span>
<span class="sd"> RETURN_NAMES (tuple): Names of the returned outputs (dynamically updated).</span>
<span class="sd"> node_configs (dict): Storage for configurations of node instances.</span>
<span class="sd"> jinja_env (SandboxedEnvironment): Sandboxed Jinja2 environment for secure template rendering.</span>
<span class="sd"> additional_context (dict): Extra context variables available in Jinja2 templates.</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">CATEGORY</span> <span class="o">=</span> <span class="s2">&quot;dv/string_operations&quot;</span>
<span class="n">FUNCTION</span> <span class="o">=</span> <span class="s2">&quot;format_string&quot;</span>
<span class="n">RETURN_TYPES</span> <span class="o">=</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="s2">&quot;STRING&quot;</span><span class="p">)</span>
<span class="n">RETURN_NAMES</span> <span class="o">=</span> <span class="p">(</span><span class="s2">&quot;formatted_string&quot;</span><span class="p">,</span> <span class="s2">&quot;saved_file_path&quot;</span><span class="p">)</span>
<span class="n">OUTPUT_IS_LIST</span> <span class="o">=</span> <span class="p">(</span><span class="kc">False</span><span class="p">,</span> <span class="kc">False</span><span class="p">)</span>
<span class="n">OUTPUT_NODE</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Store configurations for each node instance</span>
<span class="n">node_configs</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]]</span> <span class="o">=</span> <span class="p">{}</span>
<span class="c1"># Create a sandboxed Jinja2 environment for security</span>
<span class="n">jinja_env</span> <span class="o">=</span> <span class="n">sandbox</span><span class="o">.</span><span class="n">SandboxedEnvironment</span><span class="p">()</span>
<span class="c1"># Define additional context</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">time_now</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Get the current time in a formatted string.</span>
<span class="sd"> Returns:</span>
<span class="sd"> str: Current time formatted as &#39;YYYYMMDD-HHMMSS&#39;.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> timestamp = FormatString.time_now()</span>
<span class="sd"> print(timestamp) # Outputs something like: &#39;20240327-153045&#39;</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; from datetime import datetime</span>
<span class="sd"> &gt;&gt;&gt; timestamp = FormatString.time_now()</span>
<span class="sd"> &gt;&gt;&gt; assert len(timestamp) == 15 # Format YYYYMMDD-HHMMSS is 15 chars</span>
<span class="sd"> &gt;&gt;&gt; assert timestamp[8] == &#39;-&#39; # Check format separator</span>
<span class="sd"> &gt;&gt;&gt; # Verify it&#39;s roughly the current time (allowing some seconds of delay)</span>
<span class="sd"> &gt;&gt;&gt; current = datetime.now().strftime(&quot;%Y%m%d-%H%M&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert timestamp.startswith(current)</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">return</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&quot;%Y%m</span><span class="si">%d</span><span class="s2">-%H%M%S&quot;</span><span class="p">)</span>
<span class="n">additional_context</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">&quot;datetime&quot;</span><span class="p">:</span> <span class="n">datetime</span><span class="p">,</span>
<span class="s2">&quot;now&quot;</span><span class="p">:</span> <span class="n">time_now</span><span class="p">,</span> <span class="c1"># we name our custom function `time_now` as `now` so inside jinja it&#39;s `{{ now() }}`</span>
<span class="s2">&quot;random&quot;</span><span class="p">:</span> <span class="n">random</span><span class="p">,</span>
<span class="s2">&quot;math&quot;</span><span class="p">:</span> <span class="n">math</span><span class="p">,</span>
<span class="c1"># Add more modules or functions as needed</span>
<span class="p">}</span>
<span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">INPUT_TYPES</span><span class="p">(</span><span class="bp">cls</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Define the input types for the FormatString node.</span>
<span class="sd"> This method is called by ComfyUI to determine what inputs the node should have.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Dict[str, Any]: Dictionary defining the node&#39;s inputs, including template_type,</span>
<span class="sd"> template, save_path, and a hidden unique_id.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> input_types = FormatString.INPUT_TYPES()</span>
<span class="sd"> print(input_types[&quot;required&quot;][&quot;template_type&quot;]) # Outputs: ([&quot;Simple&quot;, &quot;Jinja2&quot;],)</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; input_types = FormatString.INPUT_TYPES()</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;required&quot; in input_types</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;template_type&quot; in input_types[&quot;required&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;template&quot; in input_types[&quot;required&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;save_path&quot; in input_types[&quot;required&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;hidden&quot; in input_types</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;unique_id&quot; in input_types[&quot;hidden&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert input_types[&quot;required&quot;][&quot;template_type&quot;] == ([&quot;Simple&quot;, &quot;Jinja2&quot;],)</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s2">&quot;required&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="s2">&quot;template_type&quot;</span><span class="p">:</span> <span class="p">([</span><span class="s2">&quot;Simple&quot;</span><span class="p">,</span> <span class="s2">&quot;Jinja2&quot;</span><span class="p">],),</span>
<span class="s2">&quot;template&quot;</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&quot;multiline&quot;</span><span class="p">:</span> <span class="kc">True</span><span class="p">}),</span>
<span class="s2">&quot;save_path&quot;</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&quot;default&quot;</span><span class="p">:</span> <span class="s2">&quot;&quot;</span><span class="p">}),</span>
<span class="p">},</span>
<span class="s2">&quot;hidden&quot;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&quot;unique_id&quot;</span><span class="p">:</span> <span class="s2">&quot;UNIQUE_ID&quot;</span><span class="p">},</span>
<span class="p">}</span>
<span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">IS_CHANGED</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Determine if the node should be re-executed based on input changes.</span>
<span class="sd"> This method is called by ComfyUI to check if the node needs to be re-calculated</span>
<span class="sd"> due to changes in its inputs. It forces recalculation when Jinja2 templates</span>
<span class="sd"> contain time-dependent function calls (e.g., datetime.now(), time_now()).</span>
<span class="sd"> Args:</span>
<span class="sd"> **kwargs: Keyword arguments containing the node&#39;s current inputs.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Any: A hash of inputs for caching, or a random number to force recalculation</span>
<span class="sd"> when time-dependent functions are detected.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> # This would typically be called by ComfyUI</span>
<span class="sd"> result = FormatString.IS_CHANGED(template=&quot;Hello {name}&quot;, template_type=&quot;Simple&quot;)</span>
<span class="sd"> # Returns hash of inputs for proper caching</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; # Test with Simple template</span>
<span class="sd"> &gt;&gt;&gt; result = FormatString.IS_CHANGED(template=&quot;Hello {name}&quot;, template_type=&quot;Simple&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert isinstance(result, dict)</span>
<span class="sd"> &gt;&gt;&gt; # Test with Jinja2 template containing datetime function call</span>
<span class="sd"> &gt;&gt;&gt; result = FormatString.IS_CHANGED(template=&quot;Time: {{ datetime.now() }}&quot;, template_type=&quot;Jinja2&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert isinstance(result, int) # Should return a random int to force recalculation</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">template</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;template&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">template_type</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;template_type&quot;</span><span class="p">,</span> <span class="s2">&quot;Simple&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">template</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Empty template, returning kwargs for caching&quot;</span><span class="p">)</span>
<span class="k">return</span> <span class="n">kwargs</span>
<span class="n">template_preview</span> <span class="o">=</span> <span class="n">template</span><span class="p">[:</span><span class="mi">50</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">template</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">50</span> <span class="k">else</span> <span class="n">template</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;IS_CHANGED called - template_type: </span><span class="si">{</span><span class="n">template_type</span><span class="si">}</span><span class="s2">, template: </span><span class="si">{</span><span class="n">template_preview</span><span class="si">}</span><span class="s2">...&quot;</span>
<span class="p">)</span>
<span class="c1"># Check for time-dependent function calls in Jinja2 templates</span>
<span class="k">if</span> <span class="n">template_type</span> <span class="o">==</span> <span class="s2">&quot;Jinja2&quot;</span><span class="p">:</span>
<span class="c1"># Look for actual function calls like datetime.now(), now(), or time_now()</span>
<span class="c1"># These are time-dependent and should force recalculation each time</span>
<span class="n">time_function_pattern</span> <span class="o">=</span> <span class="sa">r</span><span class="s2">&quot;\b(datetime\.now|now|time_now)\s*\(&quot;</span>
<span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">time_function_pattern</span><span class="p">,</span> <span class="n">template</span><span class="p">):</span>
<span class="c1"># Force recalculation for time-dependent templates</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Time-dependent function detected in Jinja2 template, forcing recalculation&quot;</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">random</span><span class="o">.</span><span class="n">randrange</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">maxsize</span><span class="p">)</span>
<span class="c1"># Return kwargs for proper caching - ComfyUI will hash this</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;No time-dependent functions, using cached result if inputs unchanged&quot;</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">kwargs</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">_extract_keys</span><span class="p">(</span><span class="n">template</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Extract variable names from a template string.</span>
<span class="sd"> This method parses a template string to find all variable names used in it,</span>
<span class="sd"> supporting both Python&#39;s format style {var} and Jinja2&#39;s {{ var }} syntax.</span>
<span class="sd"> Args:</span>
<span class="sd"> template (str): The template string to parse.</span>
<span class="sd"> Returns:</span>
<span class="sd"> List[str]: A list of unique variable names found in the template.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> template = &quot;Hello {name}, today is {{ datetime.now() }}&quot;</span>
<span class="sd"> keys = FormatString._extract_keys(template)</span>
<span class="sd"> print(keys) # Outputs: [&#39;name&#39;]</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; # Test simple format</span>
<span class="sd"> &gt;&gt;&gt; keys = FormatString._extract_keys(&quot;Hello {name}, your age is {age}&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert sorted(keys) == [&#39;age&#39;, &#39;name&#39;]</span>
<span class="sd"> &gt;&gt;&gt; # Test Jinja2 format</span>
<span class="sd"> &gt;&gt;&gt; keys = FormatString._extract_keys(&quot;Hello {{ name }}, {{ greeting | upper }}&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert sorted(keys) == [&#39;greeting&#39;, &#39;name&#39;]</span>
<span class="sd"> &gt;&gt;&gt; # Test mixed format</span>
<span class="sd"> &gt;&gt;&gt; keys = FormatString._extract_keys(&quot;Hello {name}, today is {{ date }}&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert sorted(keys) == [&#39;date&#39;, &#39;name&#39;]</span>
<span class="sd"> &gt;&gt;&gt; # Test with additional context (should be excluded)</span>
<span class="sd"> &gt;&gt;&gt; keys = FormatString._extract_keys(&quot;Time: {{ datetime.now() }}&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert keys == []</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">variables</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">seen</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
<span class="k">def</span><span class="w"> </span><span class="nf">add_var</span><span class="p">(</span><span class="n">var</span><span class="p">):</span>
<span class="n">var</span> <span class="o">=</span> <span class="n">var</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot;|&quot;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot;.&quot;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
<span class="k">if</span> <span class="n">var</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">seen</span> <span class="ow">and</span> <span class="n">var</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">additional_context</span><span class="p">:</span>
<span class="n">seen</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">var</span><span class="p">)</span>
<span class="n">variables</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">var</span><span class="p">)</span>
<span class="c1"># Extract variables from Jinja2 expressions {{ }}</span>
<span class="k">for</span> <span class="n">match</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span>
<span class="sa">r</span><span class="s2">&quot;\{\{\s*([\w.]+)(?:\s*\|[\w\s]+)?(?:\.[^\(\)]+\(\))?\s*\}\}&quot;</span><span class="p">,</span> <span class="n">template</span>
<span class="p">):</span>
<span class="n">add_var</span><span class="p">(</span><span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
<span class="c1"># Extract variables from f-string style { }</span>
<span class="k">for</span> <span class="n">match</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="sa">r</span><span class="s2">&quot;\{(\w+)\}&quot;</span><span class="p">,</span> <span class="n">template</span><span class="p">):</span>
<span class="n">add_var</span><span class="p">(</span><span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
<span class="c1"># Extract variables from Jinja2 control structures {% %}</span>
<span class="k">for</span> <span class="n">structure</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="sa">r</span><span class="s2">&quot;\{%.*?%\}&quot;</span><span class="p">,</span> <span class="n">template</span><span class="p">):</span>
<span class="k">for</span> <span class="n">var</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="sa">r</span><span class="s2">&quot;\b(\w+)\|\b&quot;</span><span class="p">,</span> <span class="n">structure</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">0</span><span class="p">)):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">var</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&quot;end&quot;</span><span class="p">)</span> <span class="ow">and</span> <span class="n">var</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">{</span>
<span class="s2">&quot;if&quot;</span><span class="p">,</span>
<span class="s2">&quot;else&quot;</span><span class="p">,</span>
<span class="s2">&quot;elif&quot;</span><span class="p">,</span>
<span class="s2">&quot;for&quot;</span><span class="p">,</span>
<span class="s2">&quot;in&quot;</span><span class="p">,</span>
<span class="p">}:</span>
<span class="n">add_var</span><span class="p">(</span><span class="n">var</span><span class="p">)</span>
<span class="k">return</span> <span class="n">variables</span>
<span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">format_string</span><span class="p">(</span>
<span class="bp">cls</span><span class="p">,</span>
<span class="n">template_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="n">template</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="n">save_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="n">unique_id</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span><span class="p">,</span>
<span class="o">**</span><span class="n">kwargs</span><span class="p">,</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Format a string using the specified template type and variables.</span>
<span class="sd"> This is the main method executed by the node. It formats the template using either</span>
<span class="sd"> Python&#39;s str.format() or Jinja2 templating, and optionally saves the state to disk.</span>
<span class="sd"> The node is OUTPUT_NODE=True so the formatted string also renders as a read-only</span>
<span class="sd"> text display directly on the node (no separate Show Text node needed).</span>
<span class="sd"> Args:</span>
<span class="sd"> template_type (str): Either &quot;Simple&quot; or &quot;Jinja2&quot; to specify the template engine.</span>
<span class="sd"> template (str): The template string to format.</span>
<span class="sd"> save_path (str): Optional path to save the node state. If empty, state is not saved.</span>
<span class="sd"> unique_id (str): The unique identifier for this node instance (passed by ComfyUI).</span>
<span class="sd"> **kwargs: Variable keyword arguments that provide values for template variables.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Dict[str, Any]: ``{&quot;ui&quot;: {&quot;text&quot;: [formatted_string]}, &quot;result&quot;: result}`` where</span>
<span class="sd"> ``result`` is a tuple containing the formatted string, the save path,</span>
<span class="sd"> followed by the values of input variables (in order).</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> # Simple template example</span>
<span class="sd"> ret = FormatString.format_string(</span>
<span class="sd"> template_type=&quot;Simple&quot;,</span>
<span class="sd"> template=&quot;Hello {name}, you are {age} years old&quot;,</span>
<span class="sd"> save_path=&quot;&quot;,</span>
<span class="sd"> unique_id=&quot;123&quot;,</span>
<span class="sd"> name=&quot;Alice&quot;,</span>
<span class="sd"> age=&quot;30&quot;</span>
<span class="sd"> )</span>
<span class="sd"> print(ret[&quot;result&quot;]) # Outputs: (&#39;Hello Alice, you are 30 years old&#39;, &#39;&#39;, &#39;Alice&#39;, &#39;30&#39;)</span>
<span class="sd"> # Jinja2 template example</span>
<span class="sd"> ret = FormatString.format_string(</span>
<span class="sd"> template_type=&quot;Jinja2&quot;,</span>
<span class="sd"> template=&quot;Hello {{ name }}, today is {{ datetime.now().strftime(&#39;%A&#39;) }}&quot;,</span>
<span class="sd"> save_path=&quot;&quot;,</span>
<span class="sd"> unique_id=&quot;124&quot;,</span>
<span class="sd"> name=&quot;Bob&quot;</span>
<span class="sd"> )</span>
<span class="sd"> print(ret[&quot;result&quot;]) # Outputs: (&#39;Hello Bob, today is Wednesday&#39;, &#39;&#39;, &#39;Bob&#39;)</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; # Test simple format</span>
<span class="sd"> &gt;&gt;&gt; ret = FormatString.format_string(</span>
<span class="sd"> ... template_type=&quot;Simple&quot;,</span>
<span class="sd"> ... template=&quot;Hello {name}, you are {age} years old&quot;,</span>
<span class="sd"> ... save_path=&quot;&quot;,</span>
<span class="sd"> ... name=&quot;Alice&quot;,</span>
<span class="sd"> ... age=&quot;30&quot;</span>
<span class="sd"> ... )</span>
<span class="sd"> &gt;&gt;&gt; result = ret[&quot;result&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert result[0] == &quot;Hello Alice, you are 30 years old&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert result[1] == &quot;&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert result[2] == &quot;Alice&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert result[3] == &quot;30&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert ret[&quot;ui&quot;][&quot;text&quot;][0] == &quot;Hello Alice, you are 30 years old&quot;</span>
<span class="sd"> &gt;&gt;&gt;</span>
<span class="sd"> &gt;&gt;&gt; # Test Jinja2 format with datetime (can&#39;t test exact output due to time dependency)</span>
<span class="sd"> &gt;&gt;&gt; ret = FormatString.format_string(</span>
<span class="sd"> ... template_type=&quot;Jinja2&quot;,</span>
<span class="sd"> ... template=&quot;Name: {{ name }}&quot;,</span>
<span class="sd"> ... save_path=&quot;&quot;,</span>
<span class="sd"> ... name=&quot;Bob&quot;</span>
<span class="sd"> ... )</span>
<span class="sd"> &gt;&gt;&gt; result = ret[&quot;result&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert result[0] == &quot;Name: Bob&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert result[1] == &quot;&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert result[2] == &quot;Bob&quot;</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Formatting string - type: </span><span class="si">%s</span><span class="s2">, unique_id: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">template_type</span><span class="p">,</span> <span class="n">unique_id</span>
<span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Template: </span><span class="si">%.100s</span><span class="s2">...&quot;</span><span class="p">,</span> <span class="n">template</span><span class="p">)</span>
<span class="n">keys</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_extract_keys</span><span class="p">(</span><span class="n">template</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Extracted variables: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">keys</span><span class="p">)</span>
<span class="n">input_vals</span> <span class="o">=</span> <span class="s2">&quot;, &quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">k</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">k</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">keys</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Input values: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">input_vals</span><span class="p">)</span>
<span class="c1"># CRITICAL: Update RETURN_TYPES/RETURN_NAMES before execution to ensure they match our return tuple</span>
<span class="c1"># This is necessary because update_widget might not have been called yet (e.g., on workflow load)</span>
<span class="k">if</span> <span class="n">unique_id</span><span class="p">:</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">update_widget</span><span class="p">(</span><span class="n">unique_id</span><span class="p">,</span> <span class="n">template_type</span><span class="p">,</span> <span class="n">template</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Updated RETURN_TYPES for node </span><span class="si">%s</span><span class="s2">: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">unique_id</span><span class="p">,</span> <span class="bp">cls</span><span class="o">.</span><span class="n">RETURN_TYPES</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">template_type</span> <span class="o">==</span> <span class="s2">&quot;Simple&quot;</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">formatted_string</span> <span class="o">=</span> <span class="n">template</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Simple format successful: </span><span class="si">%.50s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">formatted_string</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">KeyError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">error_msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Missing variable in Simple template: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">error_msg</span><span class="p">)</span>
<span class="k">raise</span> <span class="c1"># Re-raise for proper error handling</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">error_msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Error in Simple template: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">error_msg</span><span class="p">)</span>
<span class="n">formatted_string</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Error: </span><span class="si">{</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="k">else</span><span class="p">:</span> <span class="c1"># Jinja2</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">jinja_template</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="n">jinja_env</span><span class="o">.</span><span class="n">from_string</span><span class="p">(</span><span class="n">template</span><span class="p">)</span>
<span class="c1"># Combine user-provided kwargs with additional_context</span>
<span class="n">context</span> <span class="o">=</span> <span class="p">{</span><span class="o">**</span><span class="bp">cls</span><span class="o">.</span><span class="n">additional_context</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">}</span>
<span class="n">formatted_string</span> <span class="o">=</span> <span class="n">jinja_template</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="o">**</span><span class="n">context</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Jinja2 format successful, result length: </span><span class="si">%d</span><span class="s2">&quot;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">formatted_string</span><span class="p">)</span>
<span class="p">)</span>
<span class="k">except</span> <span class="n">exceptions</span><span class="o">.</span><span class="n">TemplateSyntaxError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">error_msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Error in Jinja2 template: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">error_msg</span><span class="p">)</span>
<span class="n">formatted_string</span> <span class="o">=</span> <span class="n">error_msg</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">error_msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Error rendering Jinja2 template: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">error_msg</span><span class="p">)</span>
<span class="n">formatted_string</span> <span class="o">=</span> <span class="n">error_msg</span>
<span class="c1"># Save the state</span>
<span class="n">save_data</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">&quot;template_type&quot;</span><span class="p">:</span> <span class="n">template_type</span><span class="p">,</span>
<span class="s2">&quot;template&quot;</span><span class="p">:</span> <span class="n">template</span><span class="p">,</span>
<span class="s2">&quot;inputs&quot;</span><span class="p">:</span> <span class="p">{</span><span class="n">k</span><span class="p">:</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">keys</span><span class="p">},</span>
<span class="p">}</span>
<span class="n">actual_save_path</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span>
<span class="k">if</span> <span class="n">save_path</span><span class="p">:</span>
<span class="n">actual_save_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
<span class="n">folder_paths</span><span class="o">.</span><span class="n">get_output_directory</span><span class="p">(),</span> <span class="n">save_path</span>
<span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">actual_save_path</span><span class="p">),</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">actual_save_path</span><span class="p">,</span> <span class="s2">&quot;w&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">json</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">save_data</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">sort_keys</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Node state saved to: </span><span class="si">{</span><span class="n">actual_save_path</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Error saving node state: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">actual_save_path</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span> <span class="c1"># Reset save_path if saving failed</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;No save_path provided, skipping state save&quot;</span><span class="p">)</span>
<span class="c1"># Return formatted_string and saved_file_path FIRST (fixed positions 0,1),</span>
<span class="c1"># then all input values (for chaining)</span>
<span class="c1"># The order must match what was set in update_widget&#39;s RETURN_TYPES/RETURN_NAMES</span>
<span class="n">result</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">formatted_string</span><span class="p">,</span>
<span class="n">actual_save_path</span><span class="p">,</span>
<span class="p">)</span> <span class="o">+</span> <span class="nb">tuple</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">))</span> <span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">keys</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Returning </span><span class="si">%d</span><span class="s2"> outputs: keys=</span><span class="si">%s</span><span class="s2">, save_path=</span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span>
<span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="p">),</span>
<span class="n">keys</span><span class="p">,</span>
<span class="n">actual_save_path</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Full result tuple length: </span><span class="si">%d</span><span class="s2">, expected: </span><span class="si">%d</span><span class="s2">&quot;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span> <span class="o">+</span> <span class="mi">2</span>
<span class="p">)</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s2">&quot;ui&quot;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&quot;text&quot;</span><span class="p">:</span> <span class="p">[</span><span class="n">formatted_string</span><span class="p">]},</span>
<span class="s2">&quot;result&quot;</span><span class="p">:</span> <span class="n">result</span><span class="p">,</span>
<span class="p">}</span>
<span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">update_widget</span><span class="p">(</span>
<span class="bp">cls</span><span class="p">,</span> <span class="n">node_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">template_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">template</span><span class="p">:</span> <span class="nb">str</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Update a node&#39;s widget configuration based on the template.</span>
<span class="sd"> This method is called when a template is changed to dynamically update the</span>
<span class="sd"> node&#39;s inputs and outputs based on the variables detected in the template.</span>
<span class="sd"> Args:</span>
<span class="sd"> node_id (str): The unique identifier of the node instance.</span>
<span class="sd"> template_type (str): The template type (&quot;Simple&quot; or &quot;Jinja2&quot;).</span>
<span class="sd"> template (str): The template string.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Dict[str, Any]: Updated configuration for the node.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> # Called via ComfyUI&#39;s web API when template changes</span>
<span class="sd"> config = FormatString.update_widget(</span>
<span class="sd"> node_id=&quot;node_123&quot;,</span>
<span class="sd"> template_type=&quot;Simple&quot;,</span>
<span class="sd"> template=&quot;Hello {name}, you are {age} years old&quot;</span>
<span class="sd"> )</span>
<span class="sd"> print(config[&quot;inputs&quot;]) # Shows inputs including &#39;name&#39; and &#39;age&#39;</span>
<span class="sd"> print(config[&quot;outputs&quot;]) # Shows outputs including extracted variables</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; config = FormatString.update_widget(</span>
<span class="sd"> ... node_id=&quot;test_node&quot;,</span>
<span class="sd"> ... template_type=&quot;Simple&quot;,</span>
<span class="sd"> ... template=&quot;Hello {name}, you are {age} years old&quot;</span>
<span class="sd"> ... )</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;name&quot; in config[&quot;inputs&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;age&quot; in config[&quot;inputs&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert len(config[&quot;outputs&quot;]) == 4 # formatted_string, saved_file_path, name, age</span>
<span class="sd"> &gt;&gt;&gt; assert config[&quot;outputs&quot;][0][&quot;name&quot;] == &quot;formatted_string&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert config[&quot;outputs&quot;][1][&quot;name&quot;] == &quot;saved_file_path&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert config[&quot;outputs&quot;][2][&quot;name&quot;] == &quot;name&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert config[&quot;outputs&quot;][3][&quot;name&quot;] == &quot;age&quot;</span>
<span class="sd"> &gt;&gt;&gt; # Check that RETURN_TYPES and RETURN_NAMES are updated</span>
<span class="sd"> &gt;&gt;&gt; assert len(FormatString.RETURN_TYPES) == 4</span>
<span class="sd"> &gt;&gt;&gt; assert len(FormatString.RETURN_NAMES) == 4</span>
<span class="sd"> &gt;&gt;&gt; assert FormatString.RETURN_NAMES[0] == &quot;formatted_string&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert FormatString.RETURN_NAMES[1] == &quot;saved_file_path&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert FormatString.RETURN_NAMES[2] == &quot;name&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert FormatString.RETURN_NAMES[3] == &quot;age&quot;</span>
<span class="sd"> &gt;&gt;&gt; # Check that node config is stored</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;test_node&quot; in FormatString.node_configs</span>
<span class="sd"> &gt;&gt;&gt; assert FormatString.node_configs[&quot;test_node&quot;] == config</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Updating widget config - node_id: </span><span class="si">%s</span><span class="s2">, template_type: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span>
<span class="n">node_id</span><span class="p">,</span>
<span class="n">template_type</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Template: </span><span class="si">%.100s</span><span class="s2">...&quot;</span><span class="p">,</span> <span class="n">template</span><span class="p">)</span>
<span class="n">keys</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_extract_keys</span><span class="p">(</span><span class="n">template</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Extracted </span><span class="si">%d</span><span class="s2"> variables from template: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">keys</span><span class="p">),</span> <span class="n">keys</span><span class="p">)</span>
<span class="n">config</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">&quot;inputs&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="s2">&quot;template_type&quot;</span><span class="p">:</span> <span class="p">([</span><span class="s2">&quot;Simple&quot;</span><span class="p">,</span> <span class="s2">&quot;Jinja2&quot;</span><span class="p">],),</span>
<span class="s2">&quot;template&quot;</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&quot;multiline&quot;</span><span class="p">:</span> <span class="kc">True</span><span class="p">}),</span>
<span class="s2">&quot;save_path&quot;</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&quot;default&quot;</span><span class="p">:</span> <span class="s2">&quot;&quot;</span><span class="p">}),</span>
<span class="p">},</span>
<span class="s2">&quot;outputs&quot;</span><span class="p">:</span> <span class="p">[],</span>
<span class="p">}</span>
<span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">keys</span><span class="p">:</span>
<span class="n">config</span><span class="p">[</span><span class="s2">&quot;inputs&quot;</span><span class="p">][</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&quot;default&quot;</span><span class="p">:</span> <span class="s2">&quot;&quot;</span><span class="p">})</span>
<span class="n">config</span><span class="p">[</span><span class="s2">&quot;outputs&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">({</span><span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="n">key</span><span class="p">,</span> <span class="s2">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;STRING&quot;</span><span class="p">})</span>
<span class="c1"># Add formatted_string and saved_file_path at the START of outputs (fixed positions)</span>
<span class="n">config</span><span class="p">[</span><span class="s2">&quot;outputs&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span><span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="s2">&quot;formatted_string&quot;</span><span class="p">,</span> <span class="s2">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;STRING&quot;</span><span class="p">},</span>
<span class="p">{</span><span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="s2">&quot;saved_file_path&quot;</span><span class="p">,</span> <span class="s2">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;STRING&quot;</span><span class="p">},</span>
<span class="p">]</span> <span class="o">+</span> <span class="n">config</span><span class="p">[</span><span class="s2">&quot;outputs&quot;</span><span class="p">]</span>
<span class="c1"># Update RETURN_TYPES and RETURN_NAMES dynamically</span>
<span class="c1"># formatted_string and saved_file_path are ALWAYS first two outputs (positions 0,1)</span>
<span class="c1"># This allows passing through variable values for chaining</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">RETURN_TYPES</span> <span class="o">=</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="s2">&quot;STRING&quot;</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">RETURN_NAMES</span> <span class="o">=</span> <span class="p">(</span><span class="s2">&quot;formatted_string&quot;</span><span class="p">,</span> <span class="s2">&quot;saved_file_path&quot;</span><span class="p">)</span> <span class="o">+</span> <span class="nb">tuple</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">OUTPUT_IS_LIST</span> <span class="o">=</span> <span class="p">(</span><span class="kc">False</span><span class="p">,)</span> <span class="o">*</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Updated RETURN_TYPES to </span><span class="si">%d</span><span class="s2"> outputs: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span>
<span class="nb">len</span><span class="p">(</span><span class="bp">cls</span><span class="o">.</span><span class="n">RETURN_TYPES</span><span class="p">),</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">RETURN_NAMES</span><span class="p">,</span>
<span class="p">)</span>
<span class="c1"># Store the configuration for this specific node</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">node_configs</span><span class="p">[</span><span class="n">node_id</span><span class="p">]</span> <span class="o">=</span> <span class="n">config</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Stored config for node </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">node_id</span><span class="p">)</span>
<span class="k">return</span> <span class="n">config</span>
<span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">get_node_config</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">node_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Get the configuration for a specific node instance.</span>
<span class="sd"> Args:</span>
<span class="sd"> node_id (str): The unique identifier of the node instance.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Dict[str, Any]: The configuration for the specified node, or an empty dict if not found.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> # After updating a node&#39;s configuration</span>
<span class="sd"> config = FormatString.get_node_config(&quot;node_123&quot;)</span>
<span class="sd"> print(config) # Shows the stored configuration for node_123</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; # First create a config</span>
<span class="sd"> &gt;&gt;&gt; _ = FormatString.update_widget(</span>
<span class="sd"> ... node_id=&quot;test_node_2&quot;,</span>
<span class="sd"> ... template_type=&quot;Simple&quot;,</span>
<span class="sd"> ... template=&quot;Hello {name}&quot;</span>
<span class="sd"> ... )</span>
<span class="sd"> &gt;&gt;&gt; # Then retrieve it</span>
<span class="sd"> &gt;&gt;&gt; config = FormatString.get_node_config(&quot;test_node_2&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;inputs&quot; in config</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;outputs&quot; in config</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;name&quot; in config[&quot;inputs&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert len(config[&quot;outputs&quot;]) == 3 # name, formatted_string, saved_file_path</span>
<span class="sd"> &gt;&gt;&gt; # Test non-existent node</span>
<span class="sd"> &gt;&gt;&gt; empty_config = FormatString.get_node_config(&quot;non_existent_node&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert empty_config == {}</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">node_configs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">node_id</span><span class="p">,</span> <span class="p">{})</span>
<span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">load_node_state</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Load a previously saved node state from disk.</span>
<span class="sd"> Args:</span>
<span class="sd"> file_path (str): Path to the saved node state JSON file.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Dict[str, Any]: The loaded node state, or an empty dict if loading failed.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> # Load a previously saved state</span>
<span class="sd"> state = FormatString.load_node_state(&quot;/path/to/saved_state.json&quot;)</span>
<span class="sd"> print(state[&quot;template&quot;]) # Shows the saved template</span>
<span class="sd"> print(state[&quot;inputs&quot;]) # Shows the saved input values</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; import tempfile</span>
<span class="sd"> &gt;&gt;&gt; import json</span>
<span class="sd"> &gt;&gt;&gt; import os</span>
<span class="sd"> &gt;&gt;&gt; # Create a temporary file with test data</span>
<span class="sd"> &gt;&gt;&gt; test_data = {</span>
<span class="sd"> ... &quot;template_type&quot;: &quot;Simple&quot;,</span>
<span class="sd"> ... &quot;template&quot;: &quot;Hello {name}&quot;,</span>
<span class="sd"> ... &quot;inputs&quot;: {&quot;name&quot;: &quot;Alice&quot;}</span>
<span class="sd"> ... }</span>
<span class="sd"> &gt;&gt;&gt; with tempfile.NamedTemporaryFile(delete=False, mode=&quot;w&quot;) as temp:</span>
<span class="sd"> ... json.dump(test_data, temp)</span>
<span class="sd"> ... temp_path = temp.name</span>
<span class="sd"> &gt;&gt;&gt; # Test loading the file</span>
<span class="sd"> &gt;&gt;&gt; state = FormatString.load_node_state(temp_path)</span>
<span class="sd"> &gt;&gt;&gt; assert state[&quot;template_type&quot;] == &quot;Simple&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert state[&quot;template&quot;] == &quot;Hello {name}&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert state[&quot;inputs&quot;][&quot;name&quot;] == &quot;Alice&quot;</span>
<span class="sd"> &gt;&gt;&gt; # Clean up</span>
<span class="sd"> &gt;&gt;&gt; os.unlink(temp_path)</span>
<span class="sd"> &gt;&gt;&gt; # Test loading non-existent file</span>
<span class="sd"> &gt;&gt;&gt; empty_state = FormatString.load_node_state(&quot;non_existent_file.json&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert empty_state == {}</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">load_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="k">return</span> <span class="n">load_data</span>
<span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
<span class="k">return</span> <span class="p">{}</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">&quot;Failed to load node state from </span><span class="si">%s</span><span class="s2">: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">file_path</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
<span class="k">return</span> <span class="p">{}</span>
</code></pre></div></td></tr></table></div>
</details>
<div class="doc doc-children">
<div class="doc doc-object doc-function">
<h3 id="comfydv.format_string.FormatString.INPUT_TYPES" class="doc doc-heading">
<code class="highlight language-python"><span class="n">INPUT_TYPES</span><span class="p">()</span></code>
<span class="doc doc-labels">
<small class="doc doc-label doc-label-classmethod"><code>classmethod</code></small>
</span>
</h3>
<div class="doc doc-contents ">
<p>Define the input types for the FormatString node.</p>
<p>This method is called by ComfyUI to determine what inputs the node should have.</p>
<p><span class="doc-section-title">Returns:</span></p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code><span title="typing.Dict">Dict</span>[<span title="str">str</span>, <span title="typing.Any">Any</span>]</code>
</td>
<td>
<div class="doc-md-description">
<p>Dict[str, Any]: Dictionary defining the node's inputs, including template_type,
template, save_path, and a hidden unique_id.</p>
</div>
</td>
</tr>
</tbody>
</table>
<details class="example" open>
<summary>Example</summary>
<div class="language-python highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">format_string</span><span class="w"> </span><span class="kn">import</span> <span class="n">FormatString</span>
<span class="n">input_types</span> <span class="o">=</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">INPUT_TYPES</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">input_types</span><span class="p">[</span><span class="s2">&quot;required&quot;</span><span class="p">][</span><span class="s2">&quot;template_type&quot;</span><span class="p">])</span> <span class="c1"># Outputs: ([&quot;Simple&quot;, &quot;Jinja2&quot;],)</span>
</code></pre></div>
</details> <!-- Example Test:
>>> input_types = FormatString.INPUT_TYPES()
>>> assert "required" in input_types
>>> assert "template_type" in input_types["required"]
>>> assert "template" in input_types["required"]
>>> assert "save_path" in input_types["required"]
>>> assert "hidden" in input_types
>>> assert "unique_id" in input_types["hidden"]
>>> assert input_types["required"]["template_type"] == (["Simple", "Jinja2"],)
-->
<details class="quote">
<summary>Source code in <code>src/comfydv/format_string.py</code></summary>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">116</span>
<span class="normal">117</span>
<span class="normal">118</span>
<span class="normal">119</span>
<span class="normal">120</span>
<span class="normal">121</span>
<span class="normal">122</span>
<span class="normal">123</span>
<span class="normal">124</span>
<span class="normal">125</span>
<span class="normal">126</span>
<span class="normal">127</span>
<span class="normal">128</span>
<span class="normal">129</span>
<span class="normal">130</span>
<span class="normal">131</span>
<span class="normal">132</span>
<span class="normal">133</span>
<span class="normal">134</span>
<span class="normal">135</span>
<span class="normal">136</span>
<span class="normal">137</span>
<span class="normal">138</span>
<span class="normal">139</span>
<span class="normal">140</span>
<span class="normal">141</span>
<span class="normal">142</span>
<span class="normal">143</span>
<span class="normal">144</span>
<span class="normal">145</span>
<span class="normal">146</span>
<span class="normal">147</span>
<span class="normal">148</span>
<span class="normal">149</span>
<span class="normal">150</span>
<span class="normal">151</span>
<span class="normal">152</span>
<span class="normal">153</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">INPUT_TYPES</span><span class="p">(</span><span class="bp">cls</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Define the input types for the FormatString node.</span>
<span class="sd"> This method is called by ComfyUI to determine what inputs the node should have.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Dict[str, Any]: Dictionary defining the node&#39;s inputs, including template_type,</span>
<span class="sd"> template, save_path, and a hidden unique_id.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> input_types = FormatString.INPUT_TYPES()</span>
<span class="sd"> print(input_types[&quot;required&quot;][&quot;template_type&quot;]) # Outputs: ([&quot;Simple&quot;, &quot;Jinja2&quot;],)</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; input_types = FormatString.INPUT_TYPES()</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;required&quot; in input_types</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;template_type&quot; in input_types[&quot;required&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;template&quot; in input_types[&quot;required&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;save_path&quot; in input_types[&quot;required&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;hidden&quot; in input_types</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;unique_id&quot; in input_types[&quot;hidden&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert input_types[&quot;required&quot;][&quot;template_type&quot;] == ([&quot;Simple&quot;, &quot;Jinja2&quot;],)</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s2">&quot;required&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="s2">&quot;template_type&quot;</span><span class="p">:</span> <span class="p">([</span><span class="s2">&quot;Simple&quot;</span><span class="p">,</span> <span class="s2">&quot;Jinja2&quot;</span><span class="p">],),</span>
<span class="s2">&quot;template&quot;</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&quot;multiline&quot;</span><span class="p">:</span> <span class="kc">True</span><span class="p">}),</span>
<span class="s2">&quot;save_path&quot;</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&quot;default&quot;</span><span class="p">:</span> <span class="s2">&quot;&quot;</span><span class="p">}),</span>
<span class="p">},</span>
<span class="s2">&quot;hidden&quot;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&quot;unique_id&quot;</span><span class="p">:</span> <span class="s2">&quot;UNIQUE_ID&quot;</span><span class="p">},</span>
<span class="p">}</span>
</code></pre></div></td></tr></table></div>
</details>
</div>
</div>
<div class="doc doc-object doc-function">
<h3 id="comfydv.format_string.FormatString.IS_CHANGED" class="doc doc-heading">
<code class="highlight language-python"><span class="n">IS_CHANGED</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span></code>
<span class="doc doc-labels">
<small class="doc doc-label doc-label-classmethod"><code>classmethod</code></small>
</span>
</h3>
<div class="doc doc-contents ">
<p>Determine if the node should be re-executed based on input changes.</p>
<p>This method is called by ComfyUI to check if the node needs to be re-calculated
due to changes in its inputs. It forces recalculation when Jinja2 templates
contain time-dependent function calls (e.g., datetime.now(), time_now()).</p>
<p><span class="doc-section-title">Parameters:</span></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code>**kwargs</code>
</td>
<td>
</td>
<td>
<div class="doc-md-description">
<p>Keyword arguments containing the node's current inputs.</p>
</div>
</td>
<td>
<code>{}</code>
</td>
</tr>
</tbody>
</table>
<p><span class="doc-section-title">Returns:</span></p>
<table>
<thead>
<tr>
<th>Name</th> <th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td><code>Any</code></td> <td>
</td>
<td>
<div class="doc-md-description">
<p>A hash of inputs for caching, or a random number to force recalculation
when time-dependent functions are detected.</p>
</div>
</td>
</tr>
</tbody>
</table>
<details class="example" open>
<summary>Example</summary>
<div class="language-python highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">format_string</span><span class="w"> </span><span class="kn">import</span> <span class="n">FormatString</span>
<span class="c1"># This would typically be called by ComfyUI</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">IS_CHANGED</span><span class="p">(</span><span class="n">template</span><span class="o">=</span><span class="s2">&quot;Hello </span><span class="si">{name}</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">template_type</span><span class="o">=</span><span class="s2">&quot;Simple&quot;</span><span class="p">)</span>
<span class="c1"># Returns hash of inputs for proper caching</span>
</code></pre></div>
</details> <!-- Example Test:
>>> # Test with Simple template
>>> result = FormatString.IS_CHANGED(template="Hello {name}", template_type="Simple")
>>> assert isinstance(result, dict)
>>> # Test with Jinja2 template containing datetime function call
>>> result = FormatString.IS_CHANGED(template="Time: {{ datetime.now() }}", template_type="Jinja2")
>>> assert isinstance(result, int) # Should return a random int to force recalculation
-->
<details class="quote">
<summary>Source code in <code>src/comfydv/format_string.py</code></summary>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">155</span>
<span class="normal">156</span>
<span class="normal">157</span>
<span class="normal">158</span>
<span class="normal">159</span>
<span class="normal">160</span>
<span class="normal">161</span>
<span class="normal">162</span>
<span class="normal">163</span>
<span class="normal">164</span>
<span class="normal">165</span>
<span class="normal">166</span>
<span class="normal">167</span>
<span class="normal">168</span>
<span class="normal">169</span>
<span class="normal">170</span>
<span class="normal">171</span>
<span class="normal">172</span>
<span class="normal">173</span>
<span class="normal">174</span>
<span class="normal">175</span>
<span class="normal">176</span>
<span class="normal">177</span>
<span class="normal">178</span>
<span class="normal">179</span>
<span class="normal">180</span>
<span class="normal">181</span>
<span class="normal">182</span>
<span class="normal">183</span>
<span class="normal">184</span>
<span class="normal">185</span>
<span class="normal">186</span>
<span class="normal">187</span>
<span class="normal">188</span>
<span class="normal">189</span>
<span class="normal">190</span>
<span class="normal">191</span>
<span class="normal">192</span>
<span class="normal">193</span>
<span class="normal">194</span>
<span class="normal">195</span>
<span class="normal">196</span>
<span class="normal">197</span>
<span class="normal">198</span>
<span class="normal">199</span>
<span class="normal">200</span>
<span class="normal">201</span>
<span class="normal">202</span>
<span class="normal">203</span>
<span class="normal">204</span>
<span class="normal">205</span>
<span class="normal">206</span>
<span class="normal">207</span>
<span class="normal">208</span>
<span class="normal">209</span>
<span class="normal">210</span>
<span class="normal">211</span>
<span class="normal">212</span>
<span class="normal">213</span>
<span class="normal">214</span>
<span class="normal">215</span>
<span class="normal">216</span>
<span class="normal">217</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">IS_CHANGED</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Determine if the node should be re-executed based on input changes.</span>
<span class="sd"> This method is called by ComfyUI to check if the node needs to be re-calculated</span>
<span class="sd"> due to changes in its inputs. It forces recalculation when Jinja2 templates</span>
<span class="sd"> contain time-dependent function calls (e.g., datetime.now(), time_now()).</span>
<span class="sd"> Args:</span>
<span class="sd"> **kwargs: Keyword arguments containing the node&#39;s current inputs.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Any: A hash of inputs for caching, or a random number to force recalculation</span>
<span class="sd"> when time-dependent functions are detected.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> # This would typically be called by ComfyUI</span>
<span class="sd"> result = FormatString.IS_CHANGED(template=&quot;Hello {name}&quot;, template_type=&quot;Simple&quot;)</span>
<span class="sd"> # Returns hash of inputs for proper caching</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; # Test with Simple template</span>
<span class="sd"> &gt;&gt;&gt; result = FormatString.IS_CHANGED(template=&quot;Hello {name}&quot;, template_type=&quot;Simple&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert isinstance(result, dict)</span>
<span class="sd"> &gt;&gt;&gt; # Test with Jinja2 template containing datetime function call</span>
<span class="sd"> &gt;&gt;&gt; result = FormatString.IS_CHANGED(template=&quot;Time: {{ datetime.now() }}&quot;, template_type=&quot;Jinja2&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert isinstance(result, int) # Should return a random int to force recalculation</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">template</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;template&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">template_type</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;template_type&quot;</span><span class="p">,</span> <span class="s2">&quot;Simple&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">template</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Empty template, returning kwargs for caching&quot;</span><span class="p">)</span>
<span class="k">return</span> <span class="n">kwargs</span>
<span class="n">template_preview</span> <span class="o">=</span> <span class="n">template</span><span class="p">[:</span><span class="mi">50</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">template</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">50</span> <span class="k">else</span> <span class="n">template</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;IS_CHANGED called - template_type: </span><span class="si">{</span><span class="n">template_type</span><span class="si">}</span><span class="s2">, template: </span><span class="si">{</span><span class="n">template_preview</span><span class="si">}</span><span class="s2">...&quot;</span>
<span class="p">)</span>
<span class="c1"># Check for time-dependent function calls in Jinja2 templates</span>
<span class="k">if</span> <span class="n">template_type</span> <span class="o">==</span> <span class="s2">&quot;Jinja2&quot;</span><span class="p">:</span>
<span class="c1"># Look for actual function calls like datetime.now(), now(), or time_now()</span>
<span class="c1"># These are time-dependent and should force recalculation each time</span>
<span class="n">time_function_pattern</span> <span class="o">=</span> <span class="sa">r</span><span class="s2">&quot;\b(datetime\.now|now|time_now)\s*\(&quot;</span>
<span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">time_function_pattern</span><span class="p">,</span> <span class="n">template</span><span class="p">):</span>
<span class="c1"># Force recalculation for time-dependent templates</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Time-dependent function detected in Jinja2 template, forcing recalculation&quot;</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">random</span><span class="o">.</span><span class="n">randrange</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">maxsize</span><span class="p">)</span>
<span class="c1"># Return kwargs for proper caching - ComfyUI will hash this</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;No time-dependent functions, using cached result if inputs unchanged&quot;</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">kwargs</span>
</code></pre></div></td></tr></table></div>
</details>
</div>
</div>
<div class="doc doc-object doc-function">
<h3 id="comfydv.format_string.FormatString.format_string" class="doc doc-heading">
<code class="highlight language-python"><span class="n">format_string</span><span class="p">(</span><span class="n">template_type</span><span class="p">,</span> <span class="n">template</span><span class="p">,</span> <span class="n">save_path</span><span class="p">,</span> <span class="n">unique_id</span><span class="o">=</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span></code>
<span class="doc doc-labels">
<small class="doc doc-label doc-label-classmethod"><code>classmethod</code></small>
</span>
</h3>
<div class="doc doc-contents ">
<p>Format a string using the specified template type and variables.</p>
<p>This is the main method executed by the node. It formats the template using either
Python's str.format() or Jinja2 templating, and optionally saves the state to disk.
The node is OUTPUT_NODE=True so the formatted string also renders as a read-only
text display directly on the node (no separate Show Text node needed).</p>
<p><span class="doc-section-title">Parameters:</span></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code>template_type</code>
</td>
<td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>Either "Simple" or "Jinja2" to specify the template engine.</p>
</div>
</td>
<td>
<em>required</em>
</td>
</tr>
<tr class="doc-section-item">
<td>
<code>template</code>
</td>
<td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>The template string to format.</p>
</div>
</td>
<td>
<em>required</em>
</td>
</tr>
<tr class="doc-section-item">
<td>
<code>save_path</code>
</td>
<td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>Optional path to save the node state. If empty, state is not saved.</p>
</div>
</td>
<td>
<em>required</em>
</td>
</tr>
<tr class="doc-section-item">
<td>
<code>unique_id</code>
</td>
<td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>The unique identifier for this node instance (passed by ComfyUI).</p>
</div>
</td>
<td>
<code>&#39;&#39;</code>
</td>
</tr>
<tr class="doc-section-item">
<td>
<code>**kwargs</code>
</td>
<td>
</td>
<td>
<div class="doc-md-description">
<p>Variable keyword arguments that provide values for template variables.</p>
</div>
</td>
<td>
<code>{}</code>
</td>
</tr>
</tbody>
</table>
<p><span class="doc-section-title">Returns:</span></p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code><span title="typing.Dict">Dict</span>[<span title="str">str</span>, <span title="typing.Any">Any</span>]</code>
</td>
<td>
<div class="doc-md-description">
<p>Dict[str, Any]: <code>{"ui": {"text": [formatted_string]}, "result": result}</code> where
<code>result</code> is a tuple containing the formatted string, the save path,
followed by the values of input variables (in order).</p>
</div>
</td>
</tr>
</tbody>
</table>
<details class="example" open>
<summary>Example</summary>
<div class="language-python highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">format_string</span><span class="w"> </span><span class="kn">import</span> <span class="n">FormatString</span>
<span class="c1"># Simple template example</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">format_string</span><span class="p">(</span>
<span class="n">template_type</span><span class="o">=</span><span class="s2">&quot;Simple&quot;</span><span class="p">,</span>
<span class="n">template</span><span class="o">=</span><span class="s2">&quot;Hello </span><span class="si">{name}</span><span class="s2">, you are </span><span class="si">{age}</span><span class="s2"> years old&quot;</span><span class="p">,</span>
<span class="n">save_path</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="p">,</span>
<span class="n">unique_id</span><span class="o">=</span><span class="s2">&quot;123&quot;</span><span class="p">,</span>
<span class="n">name</span><span class="o">=</span><span class="s2">&quot;Alice&quot;</span><span class="p">,</span>
<span class="n">age</span><span class="o">=</span><span class="s2">&quot;30&quot;</span>
<span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">ret</span><span class="p">[</span><span class="s2">&quot;result&quot;</span><span class="p">])</span> <span class="c1"># Outputs: (&#39;Hello Alice, you are 30 years old&#39;, &#39;&#39;, &#39;Alice&#39;, &#39;30&#39;)</span>
<span class="c1"># Jinja2 template example</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">format_string</span><span class="p">(</span>
<span class="n">template_type</span><span class="o">=</span><span class="s2">&quot;Jinja2&quot;</span><span class="p">,</span>
<span class="n">template</span><span class="o">=</span><span class="s2">&quot;Hello {{ name }}, today is {{ datetime.now().strftime(&#39;%A&#39;) }}&quot;</span><span class="p">,</span>
<span class="n">save_path</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="p">,</span>
<span class="n">unique_id</span><span class="o">=</span><span class="s2">&quot;124&quot;</span><span class="p">,</span>
<span class="n">name</span><span class="o">=</span><span class="s2">&quot;Bob&quot;</span>
<span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">ret</span><span class="p">[</span><span class="s2">&quot;result&quot;</span><span class="p">])</span> <span class="c1"># Outputs: (&#39;Hello Bob, today is Wednesday&#39;, &#39;&#39;, &#39;Bob&#39;)</span>
</code></pre></div>
</details> <!-- Example Test:
>>> # Test simple format
>>> ret = FormatString.format_string(
... template_type="Simple",
... template="Hello {name}, you are {age} years old",
... save_path="",
... name="Alice",
... age="30"
... )
>>> result = ret["result"]
>>> assert result[0] == "Hello Alice, you are 30 years old"
>>> assert result[1] == ""
>>> assert result[2] == "Alice"
>>> assert result[3] == "30"
>>> assert ret["ui"]["text"][0] == "Hello Alice, you are 30 years old"
>>>
>>> # Test Jinja2 format with datetime (can't test exact output due to time dependency)
>>> ret = FormatString.format_string(
... template_type="Jinja2",
... template="Name: {{ name }}",
... save_path="",
... name="Bob"
... )
>>> result = ret["result"]
>>> assert result[0] == "Name: Bob"
>>> assert result[1] == ""
>>> assert result[2] == "Bob"
-->
<details class="quote">
<summary>Source code in <code>src/comfydv/format_string.py</code></summary>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">290</span>
<span class="normal">291</span>
<span class="normal">292</span>
<span class="normal">293</span>
<span class="normal">294</span>
<span class="normal">295</span>
<span class="normal">296</span>
<span class="normal">297</span>
<span class="normal">298</span>
<span class="normal">299</span>
<span class="normal">300</span>
<span class="normal">301</span>
<span class="normal">302</span>
<span class="normal">303</span>
<span class="normal">304</span>
<span class="normal">305</span>
<span class="normal">306</span>
<span class="normal">307</span>
<span class="normal">308</span>
<span class="normal">309</span>
<span class="normal">310</span>
<span class="normal">311</span>
<span class="normal">312</span>
<span class="normal">313</span>
<span class="normal">314</span>
<span class="normal">315</span>
<span class="normal">316</span>
<span class="normal">317</span>
<span class="normal">318</span>
<span class="normal">319</span>
<span class="normal">320</span>
<span class="normal">321</span>
<span class="normal">322</span>
<span class="normal">323</span>
<span class="normal">324</span>
<span class="normal">325</span>
<span class="normal">326</span>
<span class="normal">327</span>
<span class="normal">328</span>
<span class="normal">329</span>
<span class="normal">330</span>
<span class="normal">331</span>
<span class="normal">332</span>
<span class="normal">333</span>
<span class="normal">334</span>
<span class="normal">335</span>
<span class="normal">336</span>
<span class="normal">337</span>
<span class="normal">338</span>
<span class="normal">339</span>
<span class="normal">340</span>
<span class="normal">341</span>
<span class="normal">342</span>
<span class="normal">343</span>
<span class="normal">344</span>
<span class="normal">345</span>
<span class="normal">346</span>
<span class="normal">347</span>
<span class="normal">348</span>
<span class="normal">349</span>
<span class="normal">350</span>
<span class="normal">351</span>
<span class="normal">352</span>
<span class="normal">353</span>
<span class="normal">354</span>
<span class="normal">355</span>
<span class="normal">356</span>
<span class="normal">357</span>
<span class="normal">358</span>
<span class="normal">359</span>
<span class="normal">360</span>
<span class="normal">361</span>
<span class="normal">362</span>
<span class="normal">363</span>
<span class="normal">364</span>
<span class="normal">365</span>
<span class="normal">366</span>
<span class="normal">367</span>
<span class="normal">368</span>
<span class="normal">369</span>
<span class="normal">370</span>
<span class="normal">371</span>
<span class="normal">372</span>
<span class="normal">373</span>
<span class="normal">374</span>
<span class="normal">375</span>
<span class="normal">376</span>
<span class="normal">377</span>
<span class="normal">378</span>
<span class="normal">379</span>
<span class="normal">380</span>
<span class="normal">381</span>
<span class="normal">382</span>
<span class="normal">383</span>
<span class="normal">384</span>
<span class="normal">385</span>
<span class="normal">386</span>
<span class="normal">387</span>
<span class="normal">388</span>
<span class="normal">389</span>
<span class="normal">390</span>
<span class="normal">391</span>
<span class="normal">392</span>
<span class="normal">393</span>
<span class="normal">394</span>
<span class="normal">395</span>
<span class="normal">396</span>
<span class="normal">397</span>
<span class="normal">398</span>
<span class="normal">399</span>
<span class="normal">400</span>
<span class="normal">401</span>
<span class="normal">402</span>
<span class="normal">403</span>
<span class="normal">404</span>
<span class="normal">405</span>
<span class="normal">406</span>
<span class="normal">407</span>
<span class="normal">408</span>
<span class="normal">409</span>
<span class="normal">410</span>
<span class="normal">411</span>
<span class="normal">412</span>
<span class="normal">413</span>
<span class="normal">414</span>
<span class="normal">415</span>
<span class="normal">416</span>
<span class="normal">417</span>
<span class="normal">418</span>
<span class="normal">419</span>
<span class="normal">420</span>
<span class="normal">421</span>
<span class="normal">422</span>
<span class="normal">423</span>
<span class="normal">424</span>
<span class="normal">425</span>
<span class="normal">426</span>
<span class="normal">427</span>
<span class="normal">428</span>
<span class="normal">429</span>
<span class="normal">430</span>
<span class="normal">431</span>
<span class="normal">432</span>
<span class="normal">433</span>
<span class="normal">434</span>
<span class="normal">435</span>
<span class="normal">436</span>
<span class="normal">437</span>
<span class="normal">438</span>
<span class="normal">439</span>
<span class="normal">440</span>
<span class="normal">441</span>
<span class="normal">442</span>
<span class="normal">443</span>
<span class="normal">444</span>
<span class="normal">445</span>
<span class="normal">446</span>
<span class="normal">447</span>
<span class="normal">448</span>
<span class="normal">449</span>
<span class="normal">450</span>
<span class="normal">451</span>
<span class="normal">452</span>
<span class="normal">453</span>
<span class="normal">454</span>
<span class="normal">455</span>
<span class="normal">456</span>
<span class="normal">457</span>
<span class="normal">458</span>
<span class="normal">459</span>
<span class="normal">460</span>
<span class="normal">461</span>
<span class="normal">462</span>
<span class="normal">463</span>
<span class="normal">464</span>
<span class="normal">465</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">format_string</span><span class="p">(</span>
<span class="bp">cls</span><span class="p">,</span>
<span class="n">template_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="n">template</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="n">save_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="n">unique_id</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span><span class="p">,</span>
<span class="o">**</span><span class="n">kwargs</span><span class="p">,</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Format a string using the specified template type and variables.</span>
<span class="sd"> This is the main method executed by the node. It formats the template using either</span>
<span class="sd"> Python&#39;s str.format() or Jinja2 templating, and optionally saves the state to disk.</span>
<span class="sd"> The node is OUTPUT_NODE=True so the formatted string also renders as a read-only</span>
<span class="sd"> text display directly on the node (no separate Show Text node needed).</span>
<span class="sd"> Args:</span>
<span class="sd"> template_type (str): Either &quot;Simple&quot; or &quot;Jinja2&quot; to specify the template engine.</span>
<span class="sd"> template (str): The template string to format.</span>
<span class="sd"> save_path (str): Optional path to save the node state. If empty, state is not saved.</span>
<span class="sd"> unique_id (str): The unique identifier for this node instance (passed by ComfyUI).</span>
<span class="sd"> **kwargs: Variable keyword arguments that provide values for template variables.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Dict[str, Any]: ``{&quot;ui&quot;: {&quot;text&quot;: [formatted_string]}, &quot;result&quot;: result}`` where</span>
<span class="sd"> ``result`` is a tuple containing the formatted string, the save path,</span>
<span class="sd"> followed by the values of input variables (in order).</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> # Simple template example</span>
<span class="sd"> ret = FormatString.format_string(</span>
<span class="sd"> template_type=&quot;Simple&quot;,</span>
<span class="sd"> template=&quot;Hello {name}, you are {age} years old&quot;,</span>
<span class="sd"> save_path=&quot;&quot;,</span>
<span class="sd"> unique_id=&quot;123&quot;,</span>
<span class="sd"> name=&quot;Alice&quot;,</span>
<span class="sd"> age=&quot;30&quot;</span>
<span class="sd"> )</span>
<span class="sd"> print(ret[&quot;result&quot;]) # Outputs: (&#39;Hello Alice, you are 30 years old&#39;, &#39;&#39;, &#39;Alice&#39;, &#39;30&#39;)</span>
<span class="sd"> # Jinja2 template example</span>
<span class="sd"> ret = FormatString.format_string(</span>
<span class="sd"> template_type=&quot;Jinja2&quot;,</span>
<span class="sd"> template=&quot;Hello {{ name }}, today is {{ datetime.now().strftime(&#39;%A&#39;) }}&quot;,</span>
<span class="sd"> save_path=&quot;&quot;,</span>
<span class="sd"> unique_id=&quot;124&quot;,</span>
<span class="sd"> name=&quot;Bob&quot;</span>
<span class="sd"> )</span>
<span class="sd"> print(ret[&quot;result&quot;]) # Outputs: (&#39;Hello Bob, today is Wednesday&#39;, &#39;&#39;, &#39;Bob&#39;)</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; # Test simple format</span>
<span class="sd"> &gt;&gt;&gt; ret = FormatString.format_string(</span>
<span class="sd"> ... template_type=&quot;Simple&quot;,</span>
<span class="sd"> ... template=&quot;Hello {name}, you are {age} years old&quot;,</span>
<span class="sd"> ... save_path=&quot;&quot;,</span>
<span class="sd"> ... name=&quot;Alice&quot;,</span>
<span class="sd"> ... age=&quot;30&quot;</span>
<span class="sd"> ... )</span>
<span class="sd"> &gt;&gt;&gt; result = ret[&quot;result&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert result[0] == &quot;Hello Alice, you are 30 years old&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert result[1] == &quot;&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert result[2] == &quot;Alice&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert result[3] == &quot;30&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert ret[&quot;ui&quot;][&quot;text&quot;][0] == &quot;Hello Alice, you are 30 years old&quot;</span>
<span class="sd"> &gt;&gt;&gt;</span>
<span class="sd"> &gt;&gt;&gt; # Test Jinja2 format with datetime (can&#39;t test exact output due to time dependency)</span>
<span class="sd"> &gt;&gt;&gt; ret = FormatString.format_string(</span>
<span class="sd"> ... template_type=&quot;Jinja2&quot;,</span>
<span class="sd"> ... template=&quot;Name: {{ name }}&quot;,</span>
<span class="sd"> ... save_path=&quot;&quot;,</span>
<span class="sd"> ... name=&quot;Bob&quot;</span>
<span class="sd"> ... )</span>
<span class="sd"> &gt;&gt;&gt; result = ret[&quot;result&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert result[0] == &quot;Name: Bob&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert result[1] == &quot;&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert result[2] == &quot;Bob&quot;</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Formatting string - type: </span><span class="si">%s</span><span class="s2">, unique_id: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">template_type</span><span class="p">,</span> <span class="n">unique_id</span>
<span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Template: </span><span class="si">%.100s</span><span class="s2">...&quot;</span><span class="p">,</span> <span class="n">template</span><span class="p">)</span>
<span class="n">keys</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_extract_keys</span><span class="p">(</span><span class="n">template</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Extracted variables: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">keys</span><span class="p">)</span>
<span class="n">input_vals</span> <span class="o">=</span> <span class="s2">&quot;, &quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">k</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">k</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">keys</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Input values: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">input_vals</span><span class="p">)</span>
<span class="c1"># CRITICAL: Update RETURN_TYPES/RETURN_NAMES before execution to ensure they match our return tuple</span>
<span class="c1"># This is necessary because update_widget might not have been called yet (e.g., on workflow load)</span>
<span class="k">if</span> <span class="n">unique_id</span><span class="p">:</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">update_widget</span><span class="p">(</span><span class="n">unique_id</span><span class="p">,</span> <span class="n">template_type</span><span class="p">,</span> <span class="n">template</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Updated RETURN_TYPES for node </span><span class="si">%s</span><span class="s2">: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">unique_id</span><span class="p">,</span> <span class="bp">cls</span><span class="o">.</span><span class="n">RETURN_TYPES</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">template_type</span> <span class="o">==</span> <span class="s2">&quot;Simple&quot;</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">formatted_string</span> <span class="o">=</span> <span class="n">template</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Simple format successful: </span><span class="si">%.50s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">formatted_string</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">KeyError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">error_msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Missing variable in Simple template: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">error_msg</span><span class="p">)</span>
<span class="k">raise</span> <span class="c1"># Re-raise for proper error handling</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">error_msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Error in Simple template: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">error_msg</span><span class="p">)</span>
<span class="n">formatted_string</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Error: </span><span class="si">{</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="k">else</span><span class="p">:</span> <span class="c1"># Jinja2</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">jinja_template</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="n">jinja_env</span><span class="o">.</span><span class="n">from_string</span><span class="p">(</span><span class="n">template</span><span class="p">)</span>
<span class="c1"># Combine user-provided kwargs with additional_context</span>
<span class="n">context</span> <span class="o">=</span> <span class="p">{</span><span class="o">**</span><span class="bp">cls</span><span class="o">.</span><span class="n">additional_context</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">}</span>
<span class="n">formatted_string</span> <span class="o">=</span> <span class="n">jinja_template</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="o">**</span><span class="n">context</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Jinja2 format successful, result length: </span><span class="si">%d</span><span class="s2">&quot;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">formatted_string</span><span class="p">)</span>
<span class="p">)</span>
<span class="k">except</span> <span class="n">exceptions</span><span class="o">.</span><span class="n">TemplateSyntaxError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">error_msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Error in Jinja2 template: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">error_msg</span><span class="p">)</span>
<span class="n">formatted_string</span> <span class="o">=</span> <span class="n">error_msg</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">error_msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;Error rendering Jinja2 template: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">error_msg</span><span class="p">)</span>
<span class="n">formatted_string</span> <span class="o">=</span> <span class="n">error_msg</span>
<span class="c1"># Save the state</span>
<span class="n">save_data</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">&quot;template_type&quot;</span><span class="p">:</span> <span class="n">template_type</span><span class="p">,</span>
<span class="s2">&quot;template&quot;</span><span class="p">:</span> <span class="n">template</span><span class="p">,</span>
<span class="s2">&quot;inputs&quot;</span><span class="p">:</span> <span class="p">{</span><span class="n">k</span><span class="p">:</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">keys</span><span class="p">},</span>
<span class="p">}</span>
<span class="n">actual_save_path</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span>
<span class="k">if</span> <span class="n">save_path</span><span class="p">:</span>
<span class="n">actual_save_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
<span class="n">folder_paths</span><span class="o">.</span><span class="n">get_output_directory</span><span class="p">(),</span> <span class="n">save_path</span>
<span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">actual_save_path</span><span class="p">),</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">actual_save_path</span><span class="p">,</span> <span class="s2">&quot;w&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">json</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">save_data</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">sort_keys</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Node state saved to: </span><span class="si">{</span><span class="n">actual_save_path</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Error saving node state: </span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">actual_save_path</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span> <span class="c1"># Reset save_path if saving failed</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;No save_path provided, skipping state save&quot;</span><span class="p">)</span>
<span class="c1"># Return formatted_string and saved_file_path FIRST (fixed positions 0,1),</span>
<span class="c1"># then all input values (for chaining)</span>
<span class="c1"># The order must match what was set in update_widget&#39;s RETURN_TYPES/RETURN_NAMES</span>
<span class="n">result</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">formatted_string</span><span class="p">,</span>
<span class="n">actual_save_path</span><span class="p">,</span>
<span class="p">)</span> <span class="o">+</span> <span class="nb">tuple</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">))</span> <span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">keys</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Returning </span><span class="si">%d</span><span class="s2"> outputs: keys=</span><span class="si">%s</span><span class="s2">, save_path=</span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span>
<span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="p">),</span>
<span class="n">keys</span><span class="p">,</span>
<span class="n">actual_save_path</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Full result tuple length: </span><span class="si">%d</span><span class="s2">, expected: </span><span class="si">%d</span><span class="s2">&quot;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span> <span class="o">+</span> <span class="mi">2</span>
<span class="p">)</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s2">&quot;ui&quot;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&quot;text&quot;</span><span class="p">:</span> <span class="p">[</span><span class="n">formatted_string</span><span class="p">]},</span>
<span class="s2">&quot;result&quot;</span><span class="p">:</span> <span class="n">result</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></td></tr></table></div>
</details>
</div>
</div>
<div class="doc doc-object doc-function">
<h3 id="comfydv.format_string.FormatString.get_node_config" class="doc doc-heading">
<code class="highlight language-python"><span class="n">get_node_config</span><span class="p">(</span><span class="n">node_id</span><span class="p">)</span></code>
<span class="doc doc-labels">
<small class="doc doc-label doc-label-classmethod"><code>classmethod</code></small>
</span>
</h3>
<div class="doc doc-contents ">
<p>Get the configuration for a specific node instance.</p>
<p><span class="doc-section-title">Parameters:</span></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code>node_id</code>
</td>
<td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>The unique identifier of the node instance.</p>
</div>
</td>
<td>
<em>required</em>
</td>
</tr>
</tbody>
</table>
<p><span class="doc-section-title">Returns:</span></p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code><span title="typing.Dict">Dict</span>[<span title="str">str</span>, <span title="typing.Any">Any</span>]</code>
</td>
<td>
<div class="doc-md-description">
<p>Dict[str, Any]: The configuration for the specified node, or an empty dict if not found.</p>
</div>
</td>
</tr>
</tbody>
</table>
<details class="example" open>
<summary>Example</summary>
<div class="language-python highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">format_string</span><span class="w"> </span><span class="kn">import</span> <span class="n">FormatString</span>
<span class="c1"># After updating a node&#39;s configuration</span>
<span class="n">config</span> <span class="o">=</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">get_node_config</span><span class="p">(</span><span class="s2">&quot;node_123&quot;</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">config</span><span class="p">)</span> <span class="c1"># Shows the stored configuration for node_123</span>
</code></pre></div>
</details> <!-- Example Test:
>>> # First create a config
>>> _ = FormatString.update_widget(
... node_id="test_node_2",
... template_type="Simple",
... template="Hello {name}"
... )
>>> # Then retrieve it
>>> config = FormatString.get_node_config("test_node_2")
>>> assert "inputs" in config
>>> assert "outputs" in config
>>> assert "name" in config["inputs"]
>>> assert len(config["outputs"]) == 3 # name, formatted_string, saved_file_path
>>> # Test non-existent node
>>> empty_config = FormatString.get_node_config("non_existent_node")
>>> assert empty_config == {}
-->
<details class="quote">
<summary>Source code in <code>src/comfydv/format_string.py</code></summary>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">571</span>
<span class="normal">572</span>
<span class="normal">573</span>
<span class="normal">574</span>
<span class="normal">575</span>
<span class="normal">576</span>
<span class="normal">577</span>
<span class="normal">578</span>
<span class="normal">579</span>
<span class="normal">580</span>
<span class="normal">581</span>
<span class="normal">582</span>
<span class="normal">583</span>
<span class="normal">584</span>
<span class="normal">585</span>
<span class="normal">586</span>
<span class="normal">587</span>
<span class="normal">588</span>
<span class="normal">589</span>
<span class="normal">590</span>
<span class="normal">591</span>
<span class="normal">592</span>
<span class="normal">593</span>
<span class="normal">594</span>
<span class="normal">595</span>
<span class="normal">596</span>
<span class="normal">597</span>
<span class="normal">598</span>
<span class="normal">599</span>
<span class="normal">600</span>
<span class="normal">601</span>
<span class="normal">602</span>
<span class="normal">603</span>
<span class="normal">604</span>
<span class="normal">605</span>
<span class="normal">606</span>
<span class="normal">607</span>
<span class="normal">608</span>
<span class="normal">609</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">get_node_config</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">node_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Get the configuration for a specific node instance.</span>
<span class="sd"> Args:</span>
<span class="sd"> node_id (str): The unique identifier of the node instance.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Dict[str, Any]: The configuration for the specified node, or an empty dict if not found.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> # After updating a node&#39;s configuration</span>
<span class="sd"> config = FormatString.get_node_config(&quot;node_123&quot;)</span>
<span class="sd"> print(config) # Shows the stored configuration for node_123</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; # First create a config</span>
<span class="sd"> &gt;&gt;&gt; _ = FormatString.update_widget(</span>
<span class="sd"> ... node_id=&quot;test_node_2&quot;,</span>
<span class="sd"> ... template_type=&quot;Simple&quot;,</span>
<span class="sd"> ... template=&quot;Hello {name}&quot;</span>
<span class="sd"> ... )</span>
<span class="sd"> &gt;&gt;&gt; # Then retrieve it</span>
<span class="sd"> &gt;&gt;&gt; config = FormatString.get_node_config(&quot;test_node_2&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;inputs&quot; in config</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;outputs&quot; in config</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;name&quot; in config[&quot;inputs&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert len(config[&quot;outputs&quot;]) == 3 # name, formatted_string, saved_file_path</span>
<span class="sd"> &gt;&gt;&gt; # Test non-existent node</span>
<span class="sd"> &gt;&gt;&gt; empty_config = FormatString.get_node_config(&quot;non_existent_node&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert empty_config == {}</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">node_configs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">node_id</span><span class="p">,</span> <span class="p">{})</span>
</code></pre></div></td></tr></table></div>
</details>
</div>
</div>
<div class="doc doc-object doc-function">
<h3 id="comfydv.format_string.FormatString.load_node_state" class="doc doc-heading">
<code class="highlight language-python"><span class="n">load_node_state</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span></code>
<span class="doc doc-labels">
<small class="doc doc-label doc-label-classmethod"><code>classmethod</code></small>
</span>
</h3>
<div class="doc doc-contents ">
<p>Load a previously saved node state from disk.</p>
<p><span class="doc-section-title">Parameters:</span></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code>file_path</code>
</td>
<td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>Path to the saved node state JSON file.</p>
</div>
</td>
<td>
<em>required</em>
</td>
</tr>
</tbody>
</table>
<p><span class="doc-section-title">Returns:</span></p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code><span title="typing.Dict">Dict</span>[<span title="str">str</span>, <span title="typing.Any">Any</span>]</code>
</td>
<td>
<div class="doc-md-description">
<p>Dict[str, Any]: The loaded node state, or an empty dict if loading failed.</p>
</div>
</td>
</tr>
</tbody>
</table>
<details class="example" open>
<summary>Example</summary>
<div class="language-python highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">format_string</span><span class="w"> </span><span class="kn">import</span> <span class="n">FormatString</span>
<span class="c1"># Load a previously saved state</span>
<span class="n">state</span> <span class="o">=</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">load_node_state</span><span class="p">(</span><span class="s2">&quot;/path/to/saved_state.json&quot;</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">state</span><span class="p">[</span><span class="s2">&quot;template&quot;</span><span class="p">])</span> <span class="c1"># Shows the saved template</span>
<span class="nb">print</span><span class="p">(</span><span class="n">state</span><span class="p">[</span><span class="s2">&quot;inputs&quot;</span><span class="p">])</span> <span class="c1"># Shows the saved input values</span>
</code></pre></div>
</details> <!-- Example Test:
>>> import tempfile
>>> import json
>>> import os
>>> # Create a temporary file with test data
>>> test_data = {
... "template_type": "Simple",
... "template": "Hello {name}",
... "inputs": {"name": "Alice"}
... }
>>> with tempfile.NamedTemporaryFile(delete=False, mode="w") as temp:
... json.dump(test_data, temp)
... temp_path = temp.name
>>> # Test loading the file
>>> state = FormatString.load_node_state(temp_path)
>>> assert state["template_type"] == "Simple"
>>> assert state["template"] == "Hello {name}"
>>> assert state["inputs"]["name"] == "Alice"
>>> # Clean up
>>> os.unlink(temp_path)
>>> # Test loading non-existent file
>>> empty_state = FormatString.load_node_state("non_existent_file.json")
>>> assert empty_state == {}
-->
<details class="quote">
<summary>Source code in <code>src/comfydv/format_string.py</code></summary>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">611</span>
<span class="normal">612</span>
<span class="normal">613</span>
<span class="normal">614</span>
<span class="normal">615</span>
<span class="normal">616</span>
<span class="normal">617</span>
<span class="normal">618</span>
<span class="normal">619</span>
<span class="normal">620</span>
<span class="normal">621</span>
<span class="normal">622</span>
<span class="normal">623</span>
<span class="normal">624</span>
<span class="normal">625</span>
<span class="normal">626</span>
<span class="normal">627</span>
<span class="normal">628</span>
<span class="normal">629</span>
<span class="normal">630</span>
<span class="normal">631</span>
<span class="normal">632</span>
<span class="normal">633</span>
<span class="normal">634</span>
<span class="normal">635</span>
<span class="normal">636</span>
<span class="normal">637</span>
<span class="normal">638</span>
<span class="normal">639</span>
<span class="normal">640</span>
<span class="normal">641</span>
<span class="normal">642</span>
<span class="normal">643</span>
<span class="normal">644</span>
<span class="normal">645</span>
<span class="normal">646</span>
<span class="normal">647</span>
<span class="normal">648</span>
<span class="normal">649</span>
<span class="normal">650</span>
<span class="normal">651</span>
<span class="normal">652</span>
<span class="normal">653</span>
<span class="normal">654</span>
<span class="normal">655</span>
<span class="normal">656</span>
<span class="normal">657</span>
<span class="normal">658</span>
<span class="normal">659</span>
<span class="normal">660</span>
<span class="normal">661</span>
<span class="normal">662</span>
<span class="normal">663</span>
<span class="normal">664</span>
<span class="normal">665</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">load_node_state</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Load a previously saved node state from disk.</span>
<span class="sd"> Args:</span>
<span class="sd"> file_path (str): Path to the saved node state JSON file.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Dict[str, Any]: The loaded node state, or an empty dict if loading failed.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> # Load a previously saved state</span>
<span class="sd"> state = FormatString.load_node_state(&quot;/path/to/saved_state.json&quot;)</span>
<span class="sd"> print(state[&quot;template&quot;]) # Shows the saved template</span>
<span class="sd"> print(state[&quot;inputs&quot;]) # Shows the saved input values</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; import tempfile</span>
<span class="sd"> &gt;&gt;&gt; import json</span>
<span class="sd"> &gt;&gt;&gt; import os</span>
<span class="sd"> &gt;&gt;&gt; # Create a temporary file with test data</span>
<span class="sd"> &gt;&gt;&gt; test_data = {</span>
<span class="sd"> ... &quot;template_type&quot;: &quot;Simple&quot;,</span>
<span class="sd"> ... &quot;template&quot;: &quot;Hello {name}&quot;,</span>
<span class="sd"> ... &quot;inputs&quot;: {&quot;name&quot;: &quot;Alice&quot;}</span>
<span class="sd"> ... }</span>
<span class="sd"> &gt;&gt;&gt; with tempfile.NamedTemporaryFile(delete=False, mode=&quot;w&quot;) as temp:</span>
<span class="sd"> ... json.dump(test_data, temp)</span>
<span class="sd"> ... temp_path = temp.name</span>
<span class="sd"> &gt;&gt;&gt; # Test loading the file</span>
<span class="sd"> &gt;&gt;&gt; state = FormatString.load_node_state(temp_path)</span>
<span class="sd"> &gt;&gt;&gt; assert state[&quot;template_type&quot;] == &quot;Simple&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert state[&quot;template&quot;] == &quot;Hello {name}&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert state[&quot;inputs&quot;][&quot;name&quot;] == &quot;Alice&quot;</span>
<span class="sd"> &gt;&gt;&gt; # Clean up</span>
<span class="sd"> &gt;&gt;&gt; os.unlink(temp_path)</span>
<span class="sd"> &gt;&gt;&gt; # Test loading non-existent file</span>
<span class="sd"> &gt;&gt;&gt; empty_state = FormatString.load_node_state(&quot;non_existent_file.json&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert empty_state == {}</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_path</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">load_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="k">return</span> <span class="n">load_data</span>
<span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
<span class="k">return</span> <span class="p">{}</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">&quot;Failed to load node state from </span><span class="si">%s</span><span class="s2">: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">file_path</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
<span class="k">return</span> <span class="p">{}</span>
</code></pre></div></td></tr></table></div>
</details>
</div>
</div>
<div class="doc doc-object doc-function">
<h3 id="comfydv.format_string.FormatString.time_now" class="doc doc-heading">
<code class="highlight language-python"><span class="n">time_now</span><span class="p">()</span></code>
<span class="doc doc-labels">
<small class="doc doc-label doc-label-staticmethod"><code>staticmethod</code></small>
</span>
</h3>
<div class="doc doc-contents ">
<p>Get the current time in a formatted string.</p>
<p><span class="doc-section-title">Returns:</span></p>
<table>
<thead>
<tr>
<th>Name</th> <th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td><code>str</code></td> <td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>Current time formatted as 'YYYYMMDD-HHMMSS'.</p>
</div>
</td>
</tr>
</tbody>
</table>
<details class="example" open>
<summary>Example</summary>
<div class="language-python highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">format_string</span><span class="w"> </span><span class="kn">import</span> <span class="n">FormatString</span>
<span class="n">timestamp</span> <span class="o">=</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">time_now</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">timestamp</span><span class="p">)</span> <span class="c1"># Outputs something like: &#39;20240327-153045&#39;</span>
</code></pre></div>
</details> <!-- Example Test:
>>> from datetime import datetime
>>> timestamp = FormatString.time_now()
>>> assert len(timestamp) == 15 # Format YYYYMMDD-HHMMSS is 15 chars
>>> assert timestamp[8] == '-' # Check format separator
>>> # Verify it's roughly the current time (allowing some seconds of delay)
>>> current = datetime.now().strftime("%Y%m%d-%H%M")
>>> assert timestamp.startswith(current)
-->
<details class="quote">
<summary>Source code in <code>src/comfydv/format_string.py</code></summary>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"> 80</span>
<span class="normal"> 81</span>
<span class="normal"> 82</span>
<span class="normal"> 83</span>
<span class="normal"> 84</span>
<span class="normal"> 85</span>
<span class="normal"> 86</span>
<span class="normal"> 87</span>
<span class="normal"> 88</span>
<span class="normal"> 89</span>
<span class="normal"> 90</span>
<span class="normal"> 91</span>
<span class="normal"> 92</span>
<span class="normal"> 93</span>
<span class="normal"> 94</span>
<span class="normal"> 95</span>
<span class="normal"> 96</span>
<span class="normal"> 97</span>
<span class="normal"> 98</span>
<span class="normal"> 99</span>
<span class="normal">100</span>
<span class="normal">101</span>
<span class="normal">102</span>
<span class="normal">103</span>
<span class="normal">104</span>
<span class="normal">105</span>
<span class="normal">106</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="nd">@staticmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">time_now</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Get the current time in a formatted string.</span>
<span class="sd"> Returns:</span>
<span class="sd"> str: Current time formatted as &#39;YYYYMMDD-HHMMSS&#39;.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> timestamp = FormatString.time_now()</span>
<span class="sd"> print(timestamp) # Outputs something like: &#39;20240327-153045&#39;</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; from datetime import datetime</span>
<span class="sd"> &gt;&gt;&gt; timestamp = FormatString.time_now()</span>
<span class="sd"> &gt;&gt;&gt; assert len(timestamp) == 15 # Format YYYYMMDD-HHMMSS is 15 chars</span>
<span class="sd"> &gt;&gt;&gt; assert timestamp[8] == &#39;-&#39; # Check format separator</span>
<span class="sd"> &gt;&gt;&gt; # Verify it&#39;s roughly the current time (allowing some seconds of delay)</span>
<span class="sd"> &gt;&gt;&gt; current = datetime.now().strftime(&quot;%Y%m%d-%H%M&quot;)</span>
<span class="sd"> &gt;&gt;&gt; assert timestamp.startswith(current)</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">return</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&quot;%Y%m</span><span class="si">%d</span><span class="s2">-%H%M%S&quot;</span><span class="p">)</span>
</code></pre></div></td></tr></table></div>
</details>
</div>
</div>
<div class="doc doc-object doc-function">
<h3 id="comfydv.format_string.FormatString.update_widget" class="doc doc-heading">
<code class="highlight language-python"><span class="n">update_widget</span><span class="p">(</span><span class="n">node_id</span><span class="p">,</span> <span class="n">template_type</span><span class="p">,</span> <span class="n">template</span><span class="p">)</span></code>
<span class="doc doc-labels">
<small class="doc doc-label doc-label-classmethod"><code>classmethod</code></small>
</span>
</h3>
<div class="doc doc-contents ">
<p>Update a node's widget configuration based on the template.</p>
<p>This method is called when a template is changed to dynamically update the
node's inputs and outputs based on the variables detected in the template.</p>
<p><span class="doc-section-title">Parameters:</span></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code>node_id</code>
</td>
<td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>The unique identifier of the node instance.</p>
</div>
</td>
<td>
<em>required</em>
</td>
</tr>
<tr class="doc-section-item">
<td>
<code>template_type</code>
</td>
<td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>The template type ("Simple" or "Jinja2").</p>
</div>
</td>
<td>
<em>required</em>
</td>
</tr>
<tr class="doc-section-item">
<td>
<code>template</code>
</td>
<td>
<code><span title="str">str</span></code>
</td>
<td>
<div class="doc-md-description">
<p>The template string.</p>
</div>
</td>
<td>
<em>required</em>
</td>
</tr>
</tbody>
</table>
<p><span class="doc-section-title">Returns:</span></p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code><span title="typing.Dict">Dict</span>[<span title="str">str</span>, <span title="typing.Any">Any</span>]</code>
</td>
<td>
<div class="doc-md-description">
<p>Dict[str, Any]: Updated configuration for the node.</p>
</div>
</td>
</tr>
</tbody>
</table>
<details class="example" open>
<summary>Example</summary>
<div class="language-python highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">format_string</span><span class="w"> </span><span class="kn">import</span> <span class="n">FormatString</span>
<span class="c1"># Called via ComfyUI&#39;s web API when template changes</span>
<span class="n">config</span> <span class="o">=</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">update_widget</span><span class="p">(</span>
<span class="n">node_id</span><span class="o">=</span><span class="s2">&quot;node_123&quot;</span><span class="p">,</span>
<span class="n">template_type</span><span class="o">=</span><span class="s2">&quot;Simple&quot;</span><span class="p">,</span>
<span class="n">template</span><span class="o">=</span><span class="s2">&quot;Hello </span><span class="si">{name}</span><span class="s2">, you are </span><span class="si">{age}</span><span class="s2"> years old&quot;</span>
<span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">config</span><span class="p">[</span><span class="s2">&quot;inputs&quot;</span><span class="p">])</span> <span class="c1"># Shows inputs including &#39;name&#39; and &#39;age&#39;</span>
<span class="nb">print</span><span class="p">(</span><span class="n">config</span><span class="p">[</span><span class="s2">&quot;outputs&quot;</span><span class="p">])</span> <span class="c1"># Shows outputs including extracted variables</span>
</code></pre></div>
</details> <!-- Example Test:
>>> config = FormatString.update_widget(
... node_id="test_node",
... template_type="Simple",
... template="Hello {name}, you are {age} years old"
... )
>>> assert "name" in config["inputs"]
>>> assert "age" in config["inputs"]
>>> assert len(config["outputs"]) == 4 # formatted_string, saved_file_path, name, age
>>> assert config["outputs"][0]["name"] == "formatted_string"
>>> assert config["outputs"][1]["name"] == "saved_file_path"
>>> assert config["outputs"][2]["name"] == "name"
>>> assert config["outputs"][3]["name"] == "age"
>>> # Check that RETURN_TYPES and RETURN_NAMES are updated
>>> assert len(FormatString.RETURN_TYPES) == 4
>>> assert len(FormatString.RETURN_NAMES) == 4
>>> assert FormatString.RETURN_NAMES[0] == "formatted_string"
>>> assert FormatString.RETURN_NAMES[1] == "saved_file_path"
>>> assert FormatString.RETURN_NAMES[2] == "name"
>>> assert FormatString.RETURN_NAMES[3] == "age"
>>> # Check that node config is stored
>>> assert "test_node" in FormatString.node_configs
>>> assert FormatString.node_configs["test_node"] == config
-->
<details class="quote">
<summary>Source code in <code>src/comfydv/format_string.py</code></summary>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">467</span>
<span class="normal">468</span>
<span class="normal">469</span>
<span class="normal">470</span>
<span class="normal">471</span>
<span class="normal">472</span>
<span class="normal">473</span>
<span class="normal">474</span>
<span class="normal">475</span>
<span class="normal">476</span>
<span class="normal">477</span>
<span class="normal">478</span>
<span class="normal">479</span>
<span class="normal">480</span>
<span class="normal">481</span>
<span class="normal">482</span>
<span class="normal">483</span>
<span class="normal">484</span>
<span class="normal">485</span>
<span class="normal">486</span>
<span class="normal">487</span>
<span class="normal">488</span>
<span class="normal">489</span>
<span class="normal">490</span>
<span class="normal">491</span>
<span class="normal">492</span>
<span class="normal">493</span>
<span class="normal">494</span>
<span class="normal">495</span>
<span class="normal">496</span>
<span class="normal">497</span>
<span class="normal">498</span>
<span class="normal">499</span>
<span class="normal">500</span>
<span class="normal">501</span>
<span class="normal">502</span>
<span class="normal">503</span>
<span class="normal">504</span>
<span class="normal">505</span>
<span class="normal">506</span>
<span class="normal">507</span>
<span class="normal">508</span>
<span class="normal">509</span>
<span class="normal">510</span>
<span class="normal">511</span>
<span class="normal">512</span>
<span class="normal">513</span>
<span class="normal">514</span>
<span class="normal">515</span>
<span class="normal">516</span>
<span class="normal">517</span>
<span class="normal">518</span>
<span class="normal">519</span>
<span class="normal">520</span>
<span class="normal">521</span>
<span class="normal">522</span>
<span class="normal">523</span>
<span class="normal">524</span>
<span class="normal">525</span>
<span class="normal">526</span>
<span class="normal">527</span>
<span class="normal">528</span>
<span class="normal">529</span>
<span class="normal">530</span>
<span class="normal">531</span>
<span class="normal">532</span>
<span class="normal">533</span>
<span class="normal">534</span>
<span class="normal">535</span>
<span class="normal">536</span>
<span class="normal">537</span>
<span class="normal">538</span>
<span class="normal">539</span>
<span class="normal">540</span>
<span class="normal">541</span>
<span class="normal">542</span>
<span class="normal">543</span>
<span class="normal">544</span>
<span class="normal">545</span>
<span class="normal">546</span>
<span class="normal">547</span>
<span class="normal">548</span>
<span class="normal">549</span>
<span class="normal">550</span>
<span class="normal">551</span>
<span class="normal">552</span>
<span class="normal">553</span>
<span class="normal">554</span>
<span class="normal">555</span>
<span class="normal">556</span>
<span class="normal">557</span>
<span class="normal">558</span>
<span class="normal">559</span>
<span class="normal">560</span>
<span class="normal">561</span>
<span class="normal">562</span>
<span class="normal">563</span>
<span class="normal">564</span>
<span class="normal">565</span>
<span class="normal">566</span>
<span class="normal">567</span>
<span class="normal">568</span>
<span class="normal">569</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="nd">@classmethod</span>
<span class="k">def</span><span class="w"> </span><span class="nf">update_widget</span><span class="p">(</span>
<span class="bp">cls</span><span class="p">,</span> <span class="n">node_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">template_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">template</span><span class="p">:</span> <span class="nb">str</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Update a node&#39;s widget configuration based on the template.</span>
<span class="sd"> This method is called when a template is changed to dynamically update the</span>
<span class="sd"> node&#39;s inputs and outputs based on the variables detected in the template.</span>
<span class="sd"> Args:</span>
<span class="sd"> node_id (str): The unique identifier of the node instance.</span>
<span class="sd"> template_type (str): The template type (&quot;Simple&quot; or &quot;Jinja2&quot;).</span>
<span class="sd"> template (str): The template string.</span>
<span class="sd"> Returns:</span>
<span class="sd"> Dict[str, Any]: Updated configuration for the node.</span>
<span class="sd"> Example:</span>
<span class="sd"> ```python</span>
<span class="sd"> from format_string import FormatString</span>
<span class="sd"> # Called via ComfyUI&#39;s web API when template changes</span>
<span class="sd"> config = FormatString.update_widget(</span>
<span class="sd"> node_id=&quot;node_123&quot;,</span>
<span class="sd"> template_type=&quot;Simple&quot;,</span>
<span class="sd"> template=&quot;Hello {name}, you are {age} years old&quot;</span>
<span class="sd"> )</span>
<span class="sd"> print(config[&quot;inputs&quot;]) # Shows inputs including &#39;name&#39; and &#39;age&#39;</span>
<span class="sd"> print(config[&quot;outputs&quot;]) # Shows outputs including extracted variables</span>
<span class="sd"> ```</span>
<span class="sd"> &lt;!-- Example Test:</span>
<span class="sd"> &gt;&gt;&gt; config = FormatString.update_widget(</span>
<span class="sd"> ... node_id=&quot;test_node&quot;,</span>
<span class="sd"> ... template_type=&quot;Simple&quot;,</span>
<span class="sd"> ... template=&quot;Hello {name}, you are {age} years old&quot;</span>
<span class="sd"> ... )</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;name&quot; in config[&quot;inputs&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;age&quot; in config[&quot;inputs&quot;]</span>
<span class="sd"> &gt;&gt;&gt; assert len(config[&quot;outputs&quot;]) == 4 # formatted_string, saved_file_path, name, age</span>
<span class="sd"> &gt;&gt;&gt; assert config[&quot;outputs&quot;][0][&quot;name&quot;] == &quot;formatted_string&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert config[&quot;outputs&quot;][1][&quot;name&quot;] == &quot;saved_file_path&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert config[&quot;outputs&quot;][2][&quot;name&quot;] == &quot;name&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert config[&quot;outputs&quot;][3][&quot;name&quot;] == &quot;age&quot;</span>
<span class="sd"> &gt;&gt;&gt; # Check that RETURN_TYPES and RETURN_NAMES are updated</span>
<span class="sd"> &gt;&gt;&gt; assert len(FormatString.RETURN_TYPES) == 4</span>
<span class="sd"> &gt;&gt;&gt; assert len(FormatString.RETURN_NAMES) == 4</span>
<span class="sd"> &gt;&gt;&gt; assert FormatString.RETURN_NAMES[0] == &quot;formatted_string&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert FormatString.RETURN_NAMES[1] == &quot;saved_file_path&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert FormatString.RETURN_NAMES[2] == &quot;name&quot;</span>
<span class="sd"> &gt;&gt;&gt; assert FormatString.RETURN_NAMES[3] == &quot;age&quot;</span>
<span class="sd"> &gt;&gt;&gt; # Check that node config is stored</span>
<span class="sd"> &gt;&gt;&gt; assert &quot;test_node&quot; in FormatString.node_configs</span>
<span class="sd"> &gt;&gt;&gt; assert FormatString.node_configs[&quot;test_node&quot;] == config</span>
<span class="sd"> --&gt;</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Updating widget config - node_id: </span><span class="si">%s</span><span class="s2">, template_type: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span>
<span class="n">node_id</span><span class="p">,</span>
<span class="n">template_type</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Template: </span><span class="si">%.100s</span><span class="s2">...&quot;</span><span class="p">,</span> <span class="n">template</span><span class="p">)</span>
<span class="n">keys</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_extract_keys</span><span class="p">(</span><span class="n">template</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Extracted </span><span class="si">%d</span><span class="s2"> variables from template: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">keys</span><span class="p">),</span> <span class="n">keys</span><span class="p">)</span>
<span class="n">config</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">&quot;inputs&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="s2">&quot;template_type&quot;</span><span class="p">:</span> <span class="p">([</span><span class="s2">&quot;Simple&quot;</span><span class="p">,</span> <span class="s2">&quot;Jinja2&quot;</span><span class="p">],),</span>
<span class="s2">&quot;template&quot;</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&quot;multiline&quot;</span><span class="p">:</span> <span class="kc">True</span><span class="p">}),</span>
<span class="s2">&quot;save_path&quot;</span><span class="p">:</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&quot;default&quot;</span><span class="p">:</span> <span class="s2">&quot;&quot;</span><span class="p">}),</span>
<span class="p">},</span>
<span class="s2">&quot;outputs&quot;</span><span class="p">:</span> <span class="p">[],</span>
<span class="p">}</span>
<span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">keys</span><span class="p">:</span>
<span class="n">config</span><span class="p">[</span><span class="s2">&quot;inputs&quot;</span><span class="p">][</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&quot;default&quot;</span><span class="p">:</span> <span class="s2">&quot;&quot;</span><span class="p">})</span>
<span class="n">config</span><span class="p">[</span><span class="s2">&quot;outputs&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">({</span><span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="n">key</span><span class="p">,</span> <span class="s2">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;STRING&quot;</span><span class="p">})</span>
<span class="c1"># Add formatted_string and saved_file_path at the START of outputs (fixed positions)</span>
<span class="n">config</span><span class="p">[</span><span class="s2">&quot;outputs&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span><span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="s2">&quot;formatted_string&quot;</span><span class="p">,</span> <span class="s2">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;STRING&quot;</span><span class="p">},</span>
<span class="p">{</span><span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="s2">&quot;saved_file_path&quot;</span><span class="p">,</span> <span class="s2">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;STRING&quot;</span><span class="p">},</span>
<span class="p">]</span> <span class="o">+</span> <span class="n">config</span><span class="p">[</span><span class="s2">&quot;outputs&quot;</span><span class="p">]</span>
<span class="c1"># Update RETURN_TYPES and RETURN_NAMES dynamically</span>
<span class="c1"># formatted_string and saved_file_path are ALWAYS first two outputs (positions 0,1)</span>
<span class="c1"># This allows passing through variable values for chaining</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">RETURN_TYPES</span> <span class="o">=</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,</span> <span class="s2">&quot;STRING&quot;</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="s2">&quot;STRING&quot;</span><span class="p">,)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">RETURN_NAMES</span> <span class="o">=</span> <span class="p">(</span><span class="s2">&quot;formatted_string&quot;</span><span class="p">,</span> <span class="s2">&quot;saved_file_path&quot;</span><span class="p">)</span> <span class="o">+</span> <span class="nb">tuple</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">OUTPUT_IS_LIST</span> <span class="o">=</span> <span class="p">(</span><span class="kc">False</span><span class="p">,)</span> <span class="o">*</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">keys</span><span class="p">)</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Updated RETURN_TYPES to </span><span class="si">%d</span><span class="s2"> outputs: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span>
<span class="nb">len</span><span class="p">(</span><span class="bp">cls</span><span class="o">.</span><span class="n">RETURN_TYPES</span><span class="p">),</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">RETURN_NAMES</span><span class="p">,</span>
<span class="p">)</span>
<span class="c1"># Store the configuration for this specific node</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">node_configs</span><span class="p">[</span><span class="n">node_id</span><span class="p">]</span> <span class="o">=</span> <span class="n">config</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Stored config for node </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">node_id</span><span class="p">)</span>
<span class="k">return</span> <span class="n">config</span>
</code></pre></div></td></tr></table></div>
</details>
</div>
</div>
</div>
</div>
</div>
<div class="doc doc-object doc-function">
<h2 id="comfydv.format_string.get_format_string_node_config" class="doc doc-heading">
<code class="highlight language-python"><span class="n">get_format_string_node_config</span><span class="p">(</span><span class="n">request</span><span class="p">)</span></code>
<span class="doc doc-labels">
<small class="doc doc-label doc-label-async"><code>async</code></small>
</span>
</h2>
<div class="doc doc-contents ">
<p>AIOHTTP route handler for retrieving a FormatString node's configuration.</p>
<p>This endpoint retrieves the stored configuration for a specific node instance.</p>
<p><span class="doc-section-title">Parameters:</span></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code>request</code>
</td>
<td>
<code><span title="aiohttp.web.Request">Request</span></code>
</td>
<td>
<div class="doc-md-description">
<p>The HTTP request object containing the node ID in the URL.</p>
</div>
</td>
<td>
<em>required</em>
</td>
</tr>
</tbody>
</table>
<p><span class="doc-section-title">Returns:</span></p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
</td>
<td>
<div class="doc-md-description">
<p>web.Response: JSON response with the node's configuration.</p>
</div>
</td>
</tr>
</tbody>
</table>
<details class="example" open>
<summary>Example</summary>
<p>This would typically be called by the frontend JavaScript via a GET request:</p>
<div class="language-javascript highlight"><pre><span></span><code><span class="c1">// In ComfyUI frontend</span>
<span class="nx">fetch</span><span class="p">(</span><span class="s2">&quot;/get_format_string_node_config/node_123&quot;</span><span class="p">)</span>
<span class="w"> </span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">response</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
<span class="w"> </span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">));</span>
</code></pre></div>
</details>
<details class="quote">
<summary>Source code in <code>src/comfydv/format_string.py</code></summary>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">757</span>
<span class="normal">758</span>
<span class="normal">759</span>
<span class="normal">760</span>
<span class="normal">761</span>
<span class="normal">762</span>
<span class="normal">763</span>
<span class="normal">764</span>
<span class="normal">765</span>
<span class="normal">766</span>
<span class="normal">767</span>
<span class="normal">768</span>
<span class="normal">769</span>
<span class="normal">770</span>
<span class="normal">771</span>
<span class="normal">772</span>
<span class="normal">773</span>
<span class="normal">774</span>
<span class="normal">775</span>
<span class="normal">776</span>
<span class="normal">777</span>
<span class="normal">778</span>
<span class="normal">779</span>
<span class="normal">780</span>
<span class="normal">781</span>
<span class="normal">782</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="nd">@PromptServer</span><span class="o">.</span><span class="n">instance</span><span class="o">.</span><span class="n">routes</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;/get_format_string_node_config/</span><span class="si">{node_id}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_format_string_node_config</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> AIOHTTP route handler for retrieving a FormatString node&#39;s configuration.</span>
<span class="sd"> This endpoint retrieves the stored configuration for a specific node instance.</span>
<span class="sd"> Args:</span>
<span class="sd"> request (web.Request): The HTTP request object containing the node ID in the URL.</span>
<span class="sd"> Returns:</span>
<span class="sd"> web.Response: JSON response with the node&#39;s configuration.</span>
<span class="sd"> Example:</span>
<span class="sd"> This would typically be called by the frontend JavaScript via a GET request:</span>
<span class="sd"> ```javascript</span>
<span class="sd"> // In ComfyUI frontend</span>
<span class="sd"> fetch(&quot;/get_format_string_node_config/node_123&quot;)</span>
<span class="sd"> .then(response =&gt; response.json())</span>
<span class="sd"> .then(data =&gt; console.log(data));</span>
<span class="sd"> ```</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">node_id</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">match_info</span><span class="p">[</span><span class="s2">&quot;node_id&quot;</span><span class="p">]</span>
<span class="n">config</span> <span class="o">=</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">get_node_config</span><span class="p">(</span><span class="n">node_id</span><span class="p">)</span>
<span class="k">return</span> <span class="n">web</span><span class="o">.</span><span class="n">json_response</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
</code></pre></div></td></tr></table></div>
</details>
</div>
</div>
<div class="doc doc-object doc-function">
<h2 id="comfydv.format_string.load_format_string_node" class="doc doc-heading">
<code class="highlight language-python"><span class="n">load_format_string_node</span><span class="p">(</span><span class="n">request</span><span class="p">)</span></code>
<span class="doc doc-labels">
<small class="doc doc-label doc-label-async"><code>async</code></small>
</span>
</h2>
<div class="doc doc-contents ">
<p>AIOHTTP route handler for loading a FormatString node's state from disk.</p>
<p>This endpoint receives JSON data containing a file path, then loads and returns
the node state from that file.</p>
<p><span class="doc-section-title">Parameters:</span></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code>request</code>
</td>
<td>
<code><span title="aiohttp.web.Request">Request</span></code>
</td>
<td>
<div class="doc-md-description">
<p>The HTTP request object containing JSON data.</p>
</div>
</td>
<td>
<em>required</em>
</td>
</tr>
</tbody>
</table>
<p><span class="doc-section-title">Returns:</span></p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
</td>
<td>
<div class="doc-md-description">
<p>web.Response: JSON response with the loaded node state.</p>
</div>
</td>
</tr>
</tbody>
</table>
<details class="example" open>
<summary>Example</summary>
<p>This would typically be called by the frontend JavaScript via a POST request:</p>
<div class="language-javascript highlight"><pre><span></span><code><span class="c1">// In ComfyUI frontend</span>
<span class="nx">fetch</span><span class="p">(</span><span class="s2">&quot;/load_format_string_node&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">method</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;POST&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nx">headers</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">&quot;Content-Type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;application/json&quot;</span><span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="nx">body</span><span class="o">:</span><span class="w"> </span><span class="nb">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span>
<span class="w"> </span><span class="nx">file_path</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;/path/to/saved_state.json&quot;</span>
<span class="w"> </span><span class="p">})</span>
<span class="p">}).</span><span class="nx">then</span><span class="p">(</span><span class="nx">response</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
<span class="w"> </span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">));</span>
</code></pre></div>
</details>
<details class="quote">
<summary>Source code in <code>src/comfydv/format_string.py</code></summary>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">721</span>
<span class="normal">722</span>
<span class="normal">723</span>
<span class="normal">724</span>
<span class="normal">725</span>
<span class="normal">726</span>
<span class="normal">727</span>
<span class="normal">728</span>
<span class="normal">729</span>
<span class="normal">730</span>
<span class="normal">731</span>
<span class="normal">732</span>
<span class="normal">733</span>
<span class="normal">734</span>
<span class="normal">735</span>
<span class="normal">736</span>
<span class="normal">737</span>
<span class="normal">738</span>
<span class="normal">739</span>
<span class="normal">740</span>
<span class="normal">741</span>
<span class="normal">742</span>
<span class="normal">743</span>
<span class="normal">744</span>
<span class="normal">745</span>
<span class="normal">746</span>
<span class="normal">747</span>
<span class="normal">748</span>
<span class="normal">749</span>
<span class="normal">750</span>
<span class="normal">751</span>
<span class="normal">752</span>
<span class="normal">753</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="nd">@PromptServer</span><span class="o">.</span><span class="n">instance</span><span class="o">.</span><span class="n">routes</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&quot;/load_format_string_node&quot;</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">load_format_string_node</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> AIOHTTP route handler for loading a FormatString node&#39;s state from disk.</span>
<span class="sd"> This endpoint receives JSON data containing a file path, then loads and returns</span>
<span class="sd"> the node state from that file.</span>
<span class="sd"> Args:</span>
<span class="sd"> request (web.Request): The HTTP request object containing JSON data.</span>
<span class="sd"> Returns:</span>
<span class="sd"> web.Response: JSON response with the loaded node state.</span>
<span class="sd"> Example:</span>
<span class="sd"> This would typically be called by the frontend JavaScript via a POST request:</span>
<span class="sd"> ```javascript</span>
<span class="sd"> // In ComfyUI frontend</span>
<span class="sd"> fetch(&quot;/load_format_string_node&quot;, {</span>
<span class="sd"> method: &quot;POST&quot;,</span>
<span class="sd"> headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },</span>
<span class="sd"> body: JSON.stringify({</span>
<span class="sd"> file_path: &quot;/path/to/saved_state.json&quot;</span>
<span class="sd"> })</span>
<span class="sd"> }).then(response =&gt; response.json())</span>
<span class="sd"> .then(data =&gt; console.log(data));</span>
<span class="sd"> ```</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">request</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<span class="n">file_path</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;file_path&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">state</span> <span class="o">=</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">load_node_state</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
<span class="k">return</span> <span class="n">web</span><span class="o">.</span><span class="n">json_response</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
</code></pre></div></td></tr></table></div>
</details>
</div>
</div>
<div class="doc doc-object doc-function">
<h2 id="comfydv.format_string.update_format_string_node" class="doc doc-heading">
<code class="highlight language-python"><span class="n">update_format_string_node</span><span class="p">(</span><span class="n">request</span><span class="p">)</span></code>
<span class="doc doc-labels">
<small class="doc doc-label doc-label-async"><code>async</code></small>
</span>
</h2>
<div class="doc doc-contents ">
<p>AIOHTTP route handler for updating a FormatString node's configuration.</p>
<p>This endpoint receives JSON data containing a node ID, template type, and template,
then updates the node's configuration based on the template.</p>
<p><span class="doc-section-title">Parameters:</span></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
<code>request</code>
</td>
<td>
<code><span title="aiohttp.web.Request">Request</span></code>
</td>
<td>
<div class="doc-md-description">
<p>The HTTP request object containing JSON data.</p>
</div>
</td>
<td>
<em>required</em>
</td>
</tr>
</tbody>
</table>
<p><span class="doc-section-title">Returns:</span></p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="doc-section-item">
<td>
</td>
<td>
<div class="doc-md-description">
<p>web.Response: JSON response with the updated node configuration.</p>
</div>
</td>
</tr>
</tbody>
</table>
<details class="example" open>
<summary>Example</summary>
<p>This would typically be called by the frontend JavaScript via a POST request:</p>
<div class="language-javascript highlight"><pre><span></span><code><span class="c1">// In ComfyUI frontend</span>
<span class="nx">fetch</span><span class="p">(</span><span class="s2">&quot;/update_format_string_node&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">method</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;POST&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nx">headers</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">&quot;Content-Type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;application/json&quot;</span><span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="nx">body</span><span class="o">:</span><span class="w"> </span><span class="nb">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span>
<span class="w"> </span><span class="nx">nodeId</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;node_123&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nx">template_type</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Simple&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nx">template</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;Hello {name}&quot;</span>
<span class="w"> </span><span class="p">})</span>
<span class="p">}).</span><span class="nx">then</span><span class="p">(</span><span class="nx">response</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
<span class="w"> </span><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">));</span>
</code></pre></div>
</details>
<details class="quote">
<summary>Source code in <code>src/comfydv/format_string.py</code></summary>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">669</span>
<span class="normal">670</span>
<span class="normal">671</span>
<span class="normal">672</span>
<span class="normal">673</span>
<span class="normal">674</span>
<span class="normal">675</span>
<span class="normal">676</span>
<span class="normal">677</span>
<span class="normal">678</span>
<span class="normal">679</span>
<span class="normal">680</span>
<span class="normal">681</span>
<span class="normal">682</span>
<span class="normal">683</span>
<span class="normal">684</span>
<span class="normal">685</span>
<span class="normal">686</span>
<span class="normal">687</span>
<span class="normal">688</span>
<span class="normal">689</span>
<span class="normal">690</span>
<span class="normal">691</span>
<span class="normal">692</span>
<span class="normal">693</span>
<span class="normal">694</span>
<span class="normal">695</span>
<span class="normal">696</span>
<span class="normal">697</span>
<span class="normal">698</span>
<span class="normal">699</span>
<span class="normal">700</span>
<span class="normal">701</span>
<span class="normal">702</span>
<span class="normal">703</span>
<span class="normal">704</span>
<span class="normal">705</span>
<span class="normal">706</span>
<span class="normal">707</span>
<span class="normal">708</span>
<span class="normal">709</span>
<span class="normal">710</span>
<span class="normal">711</span>
<span class="normal">712</span>
<span class="normal">713</span>
<span class="normal">714</span>
<span class="normal">715</span>
<span class="normal">716</span>
<span class="normal">717</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="nd">@PromptServer</span><span class="o">.</span><span class="n">instance</span><span class="o">.</span><span class="n">routes</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&quot;/update_format_string_node&quot;</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">update_format_string_node</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> AIOHTTP route handler for updating a FormatString node&#39;s configuration.</span>
<span class="sd"> This endpoint receives JSON data containing a node ID, template type, and template,</span>
<span class="sd"> then updates the node&#39;s configuration based on the template.</span>
<span class="sd"> Args:</span>
<span class="sd"> request (web.Request): The HTTP request object containing JSON data.</span>
<span class="sd"> Returns:</span>
<span class="sd"> web.Response: JSON response with the updated node configuration.</span>
<span class="sd"> Example:</span>
<span class="sd"> This would typically be called by the frontend JavaScript via a POST request:</span>
<span class="sd"> ```javascript</span>
<span class="sd"> // In ComfyUI frontend</span>
<span class="sd"> fetch(&quot;/update_format_string_node&quot;, {</span>
<span class="sd"> method: &quot;POST&quot;,</span>
<span class="sd"> headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },</span>
<span class="sd"> body: JSON.stringify({</span>
<span class="sd"> nodeId: &quot;node_123&quot;,</span>
<span class="sd"> template_type: &quot;Simple&quot;,</span>
<span class="sd"> template: &quot;Hello {name}&quot;</span>
<span class="sd"> })</span>
<span class="sd"> }).then(response =&gt; response.json())</span>
<span class="sd"> .then(data =&gt; console.log(data));</span>
<span class="sd"> ```</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">request</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<span class="n">node_id</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;nodeId&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">template_type</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;template_type&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">template</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;template&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span>
<span class="s2">&quot;Web API: update_format_string_node - node_id: </span><span class="si">%s</span><span class="s2">, template_type: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span>
<span class="n">node_id</span><span class="p">,</span>
<span class="n">template_type</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">updated_config</span> <span class="o">=</span> <span class="n">FormatString</span><span class="o">.</span><span class="n">update_widget</span><span class="p">(</span><span class="n">node_id</span><span class="p">,</span> <span class="n">template_type</span><span class="p">,</span> <span class="n">template</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s2">&quot;Successfully updated config for node </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="n">node_id</span><span class="p">)</span>
<span class="k">return</span> <span class="n">web</span><span class="o">.</span><span class="n">json_response</span><span class="p">(</span><span class="n">updated_config</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">&quot;Error updating node config: </span><span class="si">%s</span><span class="s2">&quot;</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">),</span> <span class="n">exc_info</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">return</span> <span class="n">web</span><span class="o">.</span><span class="n">json_response</span><span class="p">({</span><span class="s2">&quot;error&quot;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)},</span> <span class="n">status</span><span class="o">=</span><span class="mi">500</span><span class="p">)</span>
</code></pre></div></td></tr></table></div>
</details>
</div>
</div>
</div>
</div>
</div>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "../../..", "features": [], "search": "../../../assets/javascripts/workers/search.f8cc74c7.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
<script src="../../../assets/javascripts/bundle.c8b220af.min.js"></script>
</body>
</html>