Select to view content in your preferred language

Derived Output (a selection on the input) not appearing in map when "Use the selected records toggle" is used

142
5
Jump to solution
Tuesday
BaileyAOhlson
Esri Contributor

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:

This is how I defined the derived output:
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

 

Bailey Ohlson is a Product Engineer on the Data Reviewer team in the Esri Professional Services R&D Center. In her role, she develops and maintains QA/QC automation scripts, identifies software requirements, defines software test cases, and validates software repairs. Bailey earned a BS in Geology from Texas A&M University and a MA in Geography from the University of Texas at Austin.
2 Solutions

Accepted Solutions
HaydenWelch
MVP Regular Contributor

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?

View solution in original post

BaileyAOhlson
Esri Contributor

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.

 

Bailey Ohlson is a Product Engineer on the Data Reviewer team in the Esri Professional Services R&D Center. In her role, she develops and maintains QA/QC automation scripts, identifies software requirements, defines software test cases, and validates software repairs. Bailey earned a BS in Geology from Texas A&M University and a MA in Geography from the University of Texas at Austin.

View solution in original post

5 Replies
HaydenWelch
MVP Regular Contributor

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?

BaileyAOhlson
Esri Contributor

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: 

  • <class 'arcpy._mp.Layer'>
  • <class 'str'>

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.

Bailey Ohlson is a Product Engineer on the Data Reviewer team in the Esri Professional Services R&D Center. In her role, she develops and maintains QA/QC automation scripts, identifies software requirements, defines software test cases, and validates software repairs. Bailey earned a BS in Geology from Texas A&M University and a MA in Geography from the University of Texas at Austin.
HaydenWelch
MVP Regular Contributor

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)

 

BaileyAOhlson
Esri Contributor

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.

 

Bailey Ohlson is a Product Engineer on the Data Reviewer team in the Esri Professional Services R&D Center. In her role, she develops and maintains QA/QC automation scripts, identifies software requirements, defines software test cases, and validates software repairs. Bailey earned a BS in Geology from Texas A&M University and a MA in Geography from the University of Texas at Austin.
HaydenWelch
MVP Regular Contributor

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.

0 Kudos