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.
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.
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