Primary question: Is it truly required to use the SetParameter function to assign a variable to a derived output? My derived output is not updated in the map frame when SetParameter function is used in tandem with the "Use the selected records:" UI toggle button.
Hello, I am creating a python toolbox that has a Derived Output. The derived output is a selection made upon the Input Rows (feature layers or table views). However, I am noticing inconsistent behavior when I have a previous selection or Deffinition Query applied to my Input Rows when I use the "Use the selected records:" UI toggle.
When my selection or DQ is dissabled through the the "Use the selected records:" toggle, I am noticing that my python tool runs successfully but no new selection is added to my map frame. Any previous selection is cleared, but no new selection is added to the map. However, when I comment out the arcpy.SetParameter() line in my code the issue is resolved and I can create a new selection that overwrites any previous selection (which has been dissabled with the "Use the selected records:" toggle).
In the Notes at the bottom of the documentation section for script tool parameter Types you will see the second note says:
If the script tool has a derived output, you need to set the value of the derived output parameter in the script using the SetParameterAsText or SetParameter function.
However, in this case if I use the SetParameter function my derived output is not added to the map frame. How can I implement SetParameter in a way that allows the user to utilize the "Use the selected records:" toggle in GP tools? Or is this a bug with how SetParameter interacts with the toggle? I have attached the execution function for one of the tools in my toolbox below:
updatedRows = arcpy.Parameter(
displayName = "Updated Rows",
name = "out_layer_or_view",
datatype = ["GPFeatureLayer", "GPTableView"],
parameterType = "Derived",
direction = "Output"
)
updatedRows.parameterDependencies = [inputFeature.name]
updatedRows.schema.clone = True
See line 43 where I call SetParameter. If this line is removed, the "Use the selected records:" toggle can be dissabled and the new selection is displayed in the map frame. Am I using it wrong? Why isn't my new selection appearing in the map?
def execute(self, parameters, messages):
"""The source code of the tool."""
arcpy.env.workspace = parameters[0].valueAsText
inputFeatureAlias = parameters[0].valueAsText
input_file_path = arcpy.Describe(inputFeatureAlias).catalogPath
# Determine database type and find the file path for the uniquely named feature
desc = arcpy.Describe(input_file_path)
desc_workspace = desc.workspace
workspace_type = desc_workspace.workspaceFactoryProgID
if workspace_type == "esriDataSourcesGDB.FeatureServiceDBWorkspaceFactory": # A feature service
workspace_index = input_file_path.rfind("/")
else: # File, Mobile, or Enterprise Geodatabase and all other potential types
workspace_index = input_file_path.rfind("\\")
inputFeatureName = input_file_path[workspace_index + 1:]
inputFile = parameters[1].valueAsText
# Open JSON file and extract contents into selectionInfo
arcpy.AddIDMessage("INFORMATIVE", 86197, inputFile) #Opening: {inputFile}
file = open(inputFile, 'r')
selectionInfo = json.load(file)
file.close()
# Extract the value of various keys to a variable
try:
feature_class = selectionInfo["inputFeatureName"]
sql_expression = selectionInfo["expression"]
number_of_records = selectionInfo["selectedRecords"]
except Exception:
arcpy.AddIDMessage("ERROR", 80493) #Input config JSON cannot be parsed.
raise arcpy.ExecuteError
#Verify that input feature selected matches text file
if inputFeatureName != feature_class:
arcpy.AddIDMessage("ERROR", 270009, inputFeatureName, feature_class) #Input Feature or Table (inputFeatureName) does not match the inputFeatureName value (feature_class) in the file.
raise arcpy.ExecuteError
else:
pass
selection = arcpy.management.SelectLayerByAttribute(inputFeatureAlias, "NEW_SELECTION", sql_expression)
arcpy.SetParameter(parameters[2].name, selection) #Set the output parameter to the updated feature layer or tables
if selection[1] == number_of_records:
arcpy.AddIDMessage("INFORMATIVE", 270012, selection[1]) #Number of records selected: {selection[1]}
else:
arcpy.AddIDMessage("WARNING", 270008, selection[1], number_of_records) #The number of records reselected (selection[1]) does not match the number of records in the file (number_of_records).
arcpy.AddIDMessage("INFORMATIVE", 270012, selection[1]) #Number of records selected: {selection[1]}
return
Solved! Go to Solution.
I've always had better luck just passing layer objects around. That does remove the ability to check against the selection toggle in the GP Tool parameter though.
Is the SelectLayerByAttribute call returning a Result object or a Layer object?
Turns out you were right the first time! I needed to index my selection so that I was returning the map layer. Line 43 in my original code block should read:
arcpy.SetParameter(parameters[2].name, selection[0])
When I tried this earlier an extra layer would be added to my map frame and I couldn't figure out why. Turns out I just needed to clear my cache for Pro and the extra layer was no longer being added.
I think not having a derived output could be a viable option too, but I need the tool to work in ModelBuilder which necessitates having some kind of output.
I've always had better luck just passing layer objects around. That does remove the ability to check against the selection toggle in the GP Tool parameter though.
Is the SelectLayerByAttribute call returning a Result object or a Layer object?
The SelectLayerByAttribute call is returning an arcobject: <class 'arcpy.arcobjects.arcobjects.Result'>
The arcobject can be indexed to find a Layer object or a string of the number of features selected:
The problem is, if I use SetParameter with the Layer object instead of the arcobject an additional Layer for the Input is added to the map frame. I only want the selection to be made upon the original input feature layer or table view. I don't want to add a new layer to the map every time I run the tool. If I use the arcobject then the selection is made upon the input layer and an additional layer isn't added to the map frame.
Have you tried just not having the derived layer? I'm assuming derived and input are the same layer. In that case just running SelectByAttribute on the Input layer parameter/layer object will apply the selection.
You'll need to run the selection on the layer.longName attribute to have the selection show up in the map, otherwise it just updated the layer.selectionSet in memory.
It's easier to just give you some code to test:
import arcpy
from pathlib import Path
from json import load
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import arcpy.typing.describe as dt
from arcpy._mp import Layer
class Tool:
def __init__(self) -> None:
self.label = 'Tool'
def getParameterInfo(self) -> list[arcpy.Parameter]:
layer = arcpy.Parameter(
name='layer',
displayName='Layer',
direction='Input',
datatype=['GPFeatureLayer', 'GPTableView'],
parameterType='Required',
)
config = arcpy.Parameter(
name='config',
displayName='Config JSON File',
direction='Input',
datatype='DEFile',
parameterType='Required',
)
return [layer, config]
def execute(self, parameters: list[arcpy.Parameter], messages: object):
"""The source code of the tool."""
params = {p.name: p for p in parameters}
# Get Config
#Opening: {inputFile}
arcpy.AddIDMessage("INFORMATIVE", 86197, params['config'].valueAsText)
try:
selection_config: dict[str, str|int] = load(Path(params['config'].valueAsText).open('r'))
config_feature_name = selection_config["inputFeatureName"]
sql_expression = selection_config["expression"]
number_of_records = selection_config["selectedRecords"]
except Exception as e:
#Input config JSON cannot be parsed.
arcpy.AddIDMessage("ERROR", 80493)
raise arcpy.ExecuteError from e #type: ignore
# Get Layer and its datasource
layer: Layer = params['layer'].value
layer_desc: dt.Layer = arcpy.Describe(layer) #type: ignore
feature_name: str = Path(layer_desc.catalogPath).stem #type: ignore
#Verify that input feature selected matches text file
if feature_name != config_feature_name:
#Input Feature or Table (config_feature_name) does not match the inputFeatureName value (feature_class) in the file.
arcpy.AddIDMessage("ERROR", 270009, feature_name, config_feature_name)
raise arcpy.ExecuteError #type: ignore
# This will select features in the active map on the layer with the same longName (Group Layer\\LayerName or LayerName)
try:
_, count = arcpy.management.SelectLayerByAttribute(layer.longName, "NEW_SELECTION", sql_expression) #type: ignore
except Exception as e:
# Handle a failure to select the layer by name in active map here (just log it, the next step will apply the selection directly)
# This makes sure that the input layer has the selection set applied even if it's being done while the project is closed
# This method directly updates the CIM in the aprx
sel = list(oid for oid, in arcpy.da.SearchCursor(layer_desc.catalogPath, 'OID@', where_clause=str(sql_expression)))
count = len(sel)
layer.setSelectionSet(sel)
if int(count) == int(number_of_records):
#Number of records selected: {count}
arcpy.AddIDMessage("INFORMATIVE", 270012, count)
else:
#The number of records reselected (count) does not match the number of records in the file (number_of_records).
arcpy.AddIDMessage("WARNING", 270008, count, number_of_records)
#Number of records selected: {selection[1]}
arcpy.AddIDMessage("INFORMATIVE", 270012, count)
return
Sorry for the edits, I keep noticing little things after I update. I think I've stripped down your execute function to the bare bones or at least close. Assuming you want the selection to immediately show up in the active map, this will work. I also used a setSelectionSet call on the layer object as a fallback in case the tool fails to find the layer in the active map (someone switches maps before clicking run)
Turns out you were right the first time! I needed to index my selection so that I was returning the map layer. Line 43 in my original code block should read:
arcpy.SetParameter(parameters[2].name, selection[0])
When I tried this earlier an extra layer would be added to my map frame and I couldn't figure out why. Turns out I just needed to clear my cache for Pro and the extra layer was no longer being added.
I think not having a derived output could be a viable option too, but I need the tool to work in ModelBuilder which necessitates having some kind of output.
I'm glad you figured it out! arcpy can be really unergonomic at times, half of my time with it is spent just figuring out exactly what I'm getting from function calls lol. You can see that in my sample that imports the private typing and _mp modules.