Select to view content in your preferred language

Validation script to limit domain choices based on field type (e.g., integer) and domain type (e.g., coded value)

89
2
Thursday
LizAbbey
Regular Contributor

Hi,

I have a geoprocessing tool (thanks to the generous help of @SSWoodward) that uses a domain table to to define subtypes in a feature class. It uses the integer code and description from the domain table to create the subtypes, so the domain needs to be coded value and not range. The tool populates a list of available domains from the nominated workspace, and available subtype columns from the target feature class. Subtypes can only be integers, so I want to only have coded value integer domains and integer fields in my dropdown selections.

I have successfully limited the fields dropdowns to integers from the target feature class using a validation script, but my attempts with the domain list have not been successful.

Main script and validator scripts below.

import arcpy

def add_subtypes_from_domain(workspace: str, domain_name: str, target_table: str, subtype_field: str):
    """
    Adds subtypes to a feature class or table based on a coded value domain.

    Parameters:
        workspace (str): Path to the geodatabase containing the domain.
        domain_name (str): Name of the domain to extract codes from.
        target_table (str): Path to the feature class or table to update.
        subtype_field (str): Name of the integer field to use for subtypes.
    """
    arcpy.env.workspace = workspace
    temp_table = "memory\\temp_out_tbl"

    try:
        # Convert domain to table
        st_tbl = arcpy.management.DomainToTable(workspace, domain_name, temp_table, 'code', 'desc')

        # Set the subtype field
        arcpy.management.SetSubtypeField(target_table, subtype_field)

        # Add subtypes from the domain table
        with arcpy.da.SearchCursor(st_tbl, ['code', 'desc']) as cursor:
            for code, desc in cursor:
                arcpy.management.AddSubtype(target_table, code, desc)

    finally:
        # Clean up
        if 'cursor' in locals():
            del cursor
        if arcpy.Exists(temp_table):
            arcpy.management.Delete(temp_table)

if __name__ == "__main__":
    workspace = arcpy.GetParameterAsText(0)
    domain_name = arcpy.GetParameterAsText(1)
    target_table = arcpy.GetParameterAsText(2)
    subtype_field = arcpy.GetParameterAsText(3)

    add_subtypes_from_domain(workspace, domain_name, target_table, subtype_field)

 

Below is a working validation script that lists all domains as dropdowns, and only integer type fields from the target feature class (does not filter to only display coded value integer domains, but at least has dropdowns for easy selection):

import arcpy

class ToolValidator:
    def __init__(self):
        self.params = arcpy.GetParameterInfo()

    def initializeParameters(self):
        return

    def updateParameters(self):
        # Populate domain names from workspace (no type filtering)
        if self.params[0].altered and self.params[0].value:
            workspace = self.params[0].valueAsText
            arcpy.env.workspace = workspace
            try:
                domains = arcpy.da.ListDomains(workspace)
                domain_names = [d.name for d in domains]
                self.params[1].filter.list = domain_names
            except Exception:
                self.params[1].filter.list = []

        # Populate subtype field list with only Integer or SmallInteger fields
        if self.params[2].altered and self.params[2].value:
            target_table = self.params[2].valueAsText
            try:
                fields = arcpy.ListFields(target_table)
                int_fields = [
                    f.name for f in fields
                    if f.type in ["Integer", "SmallInteger"]
                ]
                self.params[3].filter.list = int_fields
            except Exception:
                self.params[3].filter.list = []

        return

    def updateMessages(self):
        return

 

Can anyone suggest some changes to the domain validator or main script to get this to limit domain dropdowns to coded value with integer field type? 

Thanks for reading through to the end.

 

0 Kudos
2 Replies
RPGIS
by MVP Regular Contributor
MVP Regular Contributor

Hi @LizAbbey,

Here is my suggestion for simplifying and perhaps help troubleshoot your code at least for the validation.

def updateParameters(self):

        # Populate domain names from workspace (no type filtering)
        db = self.params[0].valueAsText
        if db is not None:
            if '.sde' in db or '.gdb' in db:
                domains = arcpy.da.ListDomains(db)
                self.params[1].filter.list = list( set( [ d.name for d in domains for cv in d.codedValues for key, value in cv.items() if d.domainType is "CodedValue" and key in range(10) ] ) )
        
        # Populate subtype field list with only Integer or SmallInteger fields
        fcs = {}
        if db is not None:
            walk = arcpy.da.Walk(workspace, datatype="FeatureClass")
            fcs = { filename : os.path.join( root, filename ) for root, directories, filenames in walk for filename in filenames }
            self.params[2].filter.list = list( fcs )

            if self.params[2].value:
                table = self.params[2].valueAsText
                if table in fcs:
                    table = fcs[table]
                    numfields = [ f.name for f in arcpy.ListFields(table) if f.type in ["Integer", "SmallInteger"] ]
                    if len( numfields ) > 0:
                        self.params[3].filter.list = int_fields
        # My suspicion is the parameters need to be returned but I would need to double
        # check that since it has been some time since I created a python tool.
        # -- return self.params
        return

    

This should at least help with the validation and maybe help get you in the right direction. I will need to dig in deeper for the other half but I think @HaydenWelch@DanPatterson, or @jcarlson may have a better idea and also see if my suggestion is incorrect in some ways.

 

HaydenWelch
MVP Regular Contributor

Here's how I would handle this situation:

from arcpy import (
    env as _env,
    EnvManager,
    Parameter,
    GetParameterInfo,
    ListFields,
    Exists,
)

from arcpy.management import (
    SetSubtypeField,
    AddSubtype,
)
from arcpy.da import (
    ListDomains,
    Domain,
)

def add_subtypes_from_domain(workspace: str, domain_name: str, target_table: str, subtype_field: str):
    """Adds subtypes to a feature class or table based on a coded value domain.

    Parameters:
        workspace (str): Path to the geodatabase containing the domain.
        domain_name (str): Name of the domain to extract codes from.
        target_table (str): Path to the feature class or table to update.
        subtype_field (str): Name of the integer field to use for subtypes.
        
    Returns:
        ( list[Result1[str|Table]] ): The result values of the AddSubtype operations
    """
    SetSubtypeField(target_table, subtype_field)
    with EnvManager(workspace=workspace):
        return [
            AddSubtype(target_table, code, desc) 
            for domain in ListDomains(workspace)
            for code, desc in domain.codedValues.items()
            if domain.name == domain_name
        ]

def get_workspace_domains(workspace: str) -> list[str] | None:
    """Get the domains in the given workspace
    
    Parameters:
        workspace (str): Path to workspace
    
    Returns:
        ( list[str] | None ): A list of domain names in the workspace
    """
    if not Exists(workspace):
        return None
    return [domain.name for domain in ListDomains(workspace)]

def get_table_fields(table: str, types: list[str], workspace: str | None=None):
    """Gets all fields of the specified types from the input table
    
    Parameters:
        table (str): Path to table (relative if workspace is set)
        types (str): List of field types to get
        workspace (Optional[str]): Optionally set a workspace, default is arcpy.env.workspace
        
    Returns:
        ( list[str] | None ): A list of the field names in the table matching the types list
    """
    if not Exists(workspace):
        return None
    with EnvManager(workspace=workspace or _env.workspace):
        return [
            field.name
            for field in ListFields(table)
            if field.type in types
        ]
 
class ToolValidator:
    def __init__(self):
        # Map name to parameter object
        self.params = {p.name: p for p in GetParameterInfo()}

    def updateParameters(self):
        p0_name = self.params['p0_name']
        p1_name = self.params['p1_name']
        p2_name = self.params['p2_name']
        p3_name = self.params['p3_name']
        
        # Populate domain names from workspace (no type filtering)
        if p0_name.altered and p0_name.value and p0_name.filter:
            p0_name.filter.list = get_workspace_domains(
                workspace=p1_name.valueAsText
            ) or []

        # Populate subtype field list with only Integer or SmallInteger fields
        if p2_name.altered and p2_name.value and p3_name.filter:
            p3_name.filter.list = get_table_fields(
                workspace=p1_name.valueAsText, 
                table=p2_name.valueAsText, 
                types=['Integer', 'SmallInteger']
            ) or []
        return

    def updateMessages(self):
        return
0 Kudos