support warn mode when var value cannot be converted to integer (#29)

# Pull Request

## Description

When a variable cannot be converted to integer (because it is undefined, or it is a string that cannot convert) then either warn or fail with proper error message (instead of current behavior which just sort of crashes with no useful information).

`<ppp:if undefinedVar gt 1>` or `{var=hello}<ppp:if var gt 1>` will no longer crash but will warn or stop.


## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [x] This change requires a documentation update

## How Has This Been Tested?

Additional unit tests added to the test suite.

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream modules
- [x] I have checked my code and corrected any misspellings
This commit is contained in:
Antonio Cordero Balcazar
2026-03-28 15:51:29 +01:00
committed by GitHub
4 changed files with 92 additions and 13 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ With this prompt: `__quality__, 1girl, ${head:__eyes__, __hair__, __expression__
### General settings
* **Debug level**: what to write to the console. Note: in *SD.Next* debug messages only show if you launch it with the `--debug` argument.
* **What to do on invalid content warnings?**: warn on the console or stop the generation.
* **What to do on invalid content warnings?**: warn on the console or stop the generation. This also affects integer comparisons with undefined or non-numeric variables: in *warn* mode the comparison evaluates to false, in *stop* mode the generation is stopped with an error.
* **Model variant definitions**: definitions for model variants to be recognized based on strings found in the full filename.
The format for each line is (with *kind* being one of the base model identifiers or not defined):
+2
View File
@@ -211,6 +211,8 @@ The `conditionN` can be:
For a simple value the allowed operations are `eq`, `ne`, `gt`, `lt`, `ge`, `le`, `contains` and the value can be a quoted string or an integer. For a list of values the allowed operations are `contains`, `in` and the value of the variable is checked against all the elements of the list until one matches. The operation can be preceded by `not` for readability, instead of using it in the front.
When a variable used in an integer comparison is undefined or cannot be converted to an integer (e.g. it contains non-numeric text or is an empty string), the behavior depends on the `on_warning` setting: in `warn` mode the comparison evaluates to false, and in `stop` mode an error is raised.
You can also build complex conditions joining them with boolean operators and/or/not and parentheses.
The variable can be one set with the `set` or `add` commands (user variables) or you can use system variables like these (names starting with an underscore are reserved for system variables):
+13 -12
View File
@@ -1139,19 +1139,20 @@ class PromptPostProcessor: # pylint: disable=too-few-public-methods,too-many-in
)
result = False
for c in cond_value_adjusted:
var_value_adjusted = (
var_value
if isinstance(c, str)
else (
True
if isinstance(c, bool) and var_value != "false" and var_value != "" and var_value is not False
else (
False
if isinstance(c, bool) and (var_value != "true" or var_value is False)
else int(var_value)
if isinstance(c, str):
var_value_adjusted = var_value
elif isinstance(c, bool) and var_value != "false" and var_value != "" and var_value is not False:
var_value_adjusted = True
elif isinstance(c, bool) and (var_value != "true" or var_value is False):
var_value_adjusted = False
else:
try:
var_value_adjusted = int(var_value)
except (ValueError, TypeError):
self.warn_or_stop(
f"Cannot convert variable value '{var_value}' to integer for comparison"
)
)
)
return False
result = comp_ops[cond_comp](var_value_adjusted, c)
if result:
break
+76
View File
@@ -1328,6 +1328,82 @@ class TestPromptPostProcessor(TestPromptPostProcessorBase):
ppp=self.nocupppp,
)
# NaN/undefined variable integer comparison tests
def test_cmd_if_undefined_var_int_compare_warn(self): # undefined var integer compare with on_warning=warn
self.process(
PromptPair(
"<ppp:if undefined_var gt 0>YES<ppp:else>NO<ppp:/if>",
"",
),
PromptPair("NO", ""),
ppp=PromptPostProcessor(
self.ppp_logger,
self.interrupt,
self.def_env_info,
{**self.defopts, "on_warning": PromptPostProcessor.ONWARNING_CHOICES.warn.value},
self.grammar_content,
self.wildcards_obj,
self.extranetwork_maps_obj,
),
)
def test_cmd_if_undefined_var_int_compare_stop(self): # undefined var integer compare with on_warning=stop
self.process(
PromptPair(
"<ppp:if undefined_var gt 0>YES<ppp:else>NO<ppp:/if>",
"",
),
PromptPair("", ""),
interrupted=True,
)
def test_cmd_if_nonnumeric_var_int_compare_warn(self): # non-numeric var integer compare with on_warning=warn
self.process(
PromptPair(
"<ppp:set myvar>abc<ppp:/set><ppp:if myvar gt 0>YES<ppp:else>NO<ppp:/if>",
"",
),
PromptPair("NO", ""),
ppp=PromptPostProcessor(
self.ppp_logger,
self.interrupt,
self.def_env_info,
{**self.defopts, "on_warning": PromptPostProcessor.ONWARNING_CHOICES.warn.value},
self.grammar_content,
self.wildcards_obj,
self.extranetwork_maps_obj,
),
)
def test_cmd_if_nonnumeric_var_int_compare_stop(self): # non-numeric var integer compare with on_warning=stop
self.process(
PromptPair(
"<ppp:set myvar>abc<ppp:/set><ppp:if myvar gt 0>YES<ppp:else>NO<ppp:/if>",
"",
),
PromptPair("", ""),
interrupted=True,
)
def test_cmd_if_empty_var_int_compare(self): # empty string var integer compare with on_warning=warn
self.process(
PromptPair(
"<ppp:set myvar><ppp:/set><ppp:if myvar gt 0>YES<ppp:else>NO<ppp:/if>",
"",
),
PromptPair("NO", ""),
ppp=PromptPostProcessor(
self.ppp_logger,
self.interrupt,
self.def_env_info,
{**self.defopts, "on_warning": PromptPostProcessor.ONWARNING_CHOICES.warn.value},
self.grammar_content,
self.wildcards_obj,
self.extranetwork_maps_obj,
),
)
if __name__ == "__main__":
unittest.main()