I'm currently acquainting myself with the parameter and parameter validation methods of the tool class and I can't quite figure out what's required to get error/warning messages to properly populate and how updateParameters and updateMessages differ for setErrorMessage/setWarningMessage. Either I get no message, the message doesn't update on value change, or its the wrong one. What is the correct code to make sure that a parameter is properly validated after it changes and then displays the proper message (if any)?
For a simple example, I just want to validate that a given parameter is in a list of strings:
project_ids = ['123', '456', '789']
class tool:
def __init__(self):
"""Define the tool (tool name is the name of the class)."""
self.label = 'test tool'
self.description = "test"
def getParameterInfo(self) -> list:
"""Define the tool parameters."""
project_id = arcpy.Parameter(name="project ID",
displayName="project ID",
datatype="GPString",
parameterType="Required", # Required|Optional|Derived
direction="Input", # Input|Output
)
return [project_id]
def isLicensed(self):
"""Set whether the tool is licensed to execute."""
return True
def updateParameters(self, parameters):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
if parameters[0].altered:
if parameters[0].value is None:
parameters[0].setErrorMessage("No Project Specified")
elif parameters[0].valueAsText not in project_ids:
parameters[0].setErrorMessage('Unrecognized project ID')
return
def updateMessages(self, parameters):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
# The only time I got messages to show up was when I put the code block from updateParameters here
return
@staticmethod
def execute(self, parameters, messages=None):
"""The source code of the tool."""
project_id = parameters[0]
test_function(project_id)
return
def postExecute(self, parameters):
"""This method takes place after outputs are processed and
added to the display."""
return
Solved! Go to Solution.
Why are you using updateMessages and updateParameters for this? You can specify a value list in the parameter definition:
import arcpy
class Tool(object):
def __init__(self):
"""Define the tool (tool name is the name of the class)."""
self.label = 'test tool'
self.description = "test"
self.project_ids = ['123', '456', '789']
def getParameterInfo(self) -> list:
"""Define the tool parameters."""
project_id = \
arcpy.Parameter(
name="project ID",
displayName="project ID",
datatype="GPString",
parameterType="Required", # Required|Optional|Derived
direction="Input", # Input|Output
)
project_id.filter.type = "ValueList"
project_id.filter.list = self.project_ids
return [project_id]
def execute(self, parameters, messages) -> None:
"""Execute the tool."""
project_id = parameters[0].value
arcpy.AddMessage(f"project_id: {project_id}")
return
If you have more checks you need to do and actually need the updateMessages to return a more specific message to the user, then @BlakeTerhune 's or @AlfredBaldenweck's answers are the way to go, but avoiding those functions is totally valid if you can enforce the filter in the parameter definition.
Addendum: for some reason, the .altered value of a parameter is not reliable. I've never had luck getting it to work. The best way is to skip checking for altered status and just stick with checking the value. I think this is because Arcpy re-initializes the Parameter objects frequently and sets altered to False by default. You can emulate this by storing the state of the parameters outside the tool object and checking against this 'cache' if you have expensive validation methods.
This is what I do and it seems to work. Maybe you're just missing clearMessage()
def updateParameters(self, parameters):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
project_id = parameters[0].valueAsText
valid_project_ids = ['123', '456', '789']
error_msg = f'Unrecognized project ID; please enter one of the following: {valid_project_ids}'
if project_id:
if project_id not in valid_project_ids:
project_id.setErrorMessage(error_msg)
else:
project_id.clearMessage()
else:
project_id.setErrorMessage(error_msg)
return
TLDR:
Longer message with how I got to that conclusion:
So, I think your biggest problem in this specific case is that you're using updateParameters() for messaging instead of updateMessages().
project_ids = ['123', '456', '789']
def updateMessages(self, parameters):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
if parameters[0].value:
if parameters[0].valueAsText not in project_ids:
parameters[0].setErrorMessage('Unrecognized project ID')
else:
parameters[0].setWarningMessage(parameters[0].valueAsText)
# I can't get a default message to show up
#else:
# parameters[0].setErrorMessage("No Project Specified")
return
The other issue, and one I couldn't initially solve to my satisfaction, is that you're trying to set a default message to show before anything happens.
I played around a bit, and it seemed to only be a problem if you use setErrorMessage(); setWarningMessage() works just fine as a default. However, setWarningMessage() will override the required bit of the parameter, allowing you to run the tool without giving it a value.
project_ids = ['123', '456', '789']
def updateMessages(self, parameters):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
if parameters[0].value:
if parameters[0].valueAsText not in project_ids:
parameters[0].setErrorMessage('Unrecognized project ID')
# else:
# parameters[0].setWarningMessage(parameters[0].valueAsText)
else:
parameters[0].setWarningMessage("No Project Specified")
return
So, I got to thinking and experimenting, and I think the true issue is actually not in updateParameters() or updateMessages(), but in getParameterInfo().
If you set your parameter to "Optional", rather than "Required", the issue of setting an error message disappears.
project_ids = ['123', '456', '789']
def getParameterInfo(self) -> list:
"""Define the tool parameters."""
project_id = arcpy.Parameter(name="projectID",
displayName="project ID",
datatype="GPString",
parameterType="Optional",
direction="Input",
)
params = [project_id]
return params
def updateMessages(self, parameters):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
if parameters[0].value:
if parameters[0].valueAsText not in project_ids:
parameters[0].setErrorMessage('Unrecognized project ID')
# else:
# parameters[0].setWarningMessage(parameters[0].valueAsText)
else:
# Both of these worked for me.
# The former is to conditionally set an optional parameter
# to "required", the latter just sets an error message.
# One or both is fine.
##parameters[0].setIDMessage('ERROR', 735)
parameters[0].setErrorMessage("No Project Specified")
return
Why are you using updateMessages and updateParameters for this? You can specify a value list in the parameter definition:
import arcpy
class Tool(object):
def __init__(self):
"""Define the tool (tool name is the name of the class)."""
self.label = 'test tool'
self.description = "test"
self.project_ids = ['123', '456', '789']
def getParameterInfo(self) -> list:
"""Define the tool parameters."""
project_id = \
arcpy.Parameter(
name="project ID",
displayName="project ID",
datatype="GPString",
parameterType="Required", # Required|Optional|Derived
direction="Input", # Input|Output
)
project_id.filter.type = "ValueList"
project_id.filter.list = self.project_ids
return [project_id]
def execute(self, parameters, messages) -> None:
"""Execute the tool."""
project_id = parameters[0].value
arcpy.AddMessage(f"project_id: {project_id}")
return
If you have more checks you need to do and actually need the updateMessages to return a more specific message to the user, then @BlakeTerhune 's or @AlfredBaldenweck's answers are the way to go, but avoiding those functions is totally valid if you can enforce the filter in the parameter definition.
Addendum: for some reason, the .altered value of a parameter is not reliable. I've never had luck getting it to work. The best way is to skip checking for altered status and just stick with checking the value. I think this is because Arcpy re-initializes the Parameter objects frequently and sets altered to False by default. You can emulate this by storing the state of the parameters outside the tool object and checking against this 'cache' if you have expensive validation methods.
I'm just trying to adapt what I've found in the few examples of tools/toolboxes I've found that I can wrap my head around. Understanding it now would mean in the future when I need to use those functions, I'll be able to employ them. Thank you for the solutions; messages are showing up as I'd like them to. @BlakeTerhune @AlfredBaldenweck
However now I'm receiving a TypeError concerning the parameter:
project_id = parameters[0].value
~~~~~~~~~~^^^
TypeError: 'geoprocessing messages object' object is not subscriptable
GetParameterInfo is returning [project_id] so I'm not sure why I can't subscript it unless single parameters are being...delisted, somehow? 'Parameters' without an index doesn't work either.
for future reference the error was caused by the existence of a "@staticmethod" decorator on the execute function.
I didn't even notice that...
If you want an explanation as to why, the static method decorator doesn't expect an implicit 'self' argument, so the function call passed the parameters to the 'self' argument, the messages to the 'parameters' argument and None to the 'messages' argument
def test_function(**args): ...
class Tool:
def __init__(self): ...
@staticmethod
def static_execute(self, parameters, messages=None):
""" Broken execute """
project_id = parameters[0].value # This raises a TypeError
project_id = self[0].value # This works
test_function(project_id)
return
def execute(self, parameters, messages):
""" Working execute """
project_id = parameters[0].value # This works