2

I am currently attempting to create a Python Add_in tool which will perform a set of geoprocessing operations resulting in new values for a specific !shape.area!acres calculation on a new layer and then update rows in an existing feature class with these new values. I am using da.UpdateCursor as my edit tool to update the rows in the feature class from a dictionary of the new values. Basically, my script runs through all of the geoprocessing operations just fine and then either stops or skips over the da.UpdateCursor w/out throwing any errors. The last MessageBox trap that fires is right before the edit.startEditing, after that the code stops in the mxd w/ no errors.

I am open to any suggestions as to modifying the process as I am still fairly new to python programming and many of you might find my methods a little clunky. Also, this a trial tool for intended for later use on a versioned Feature Class in SDE once I get the process and methods understood.

I am using ArcGIS 10.2.1 for Desktop, with Windows 7.

# Import arcpy module
import arcpy, traceback
import pythonaddins
import os, sys
from arcpy import env
arcpy.env.overwriteOutput = True
arcpy.env.addOutputsToMap = False
workspace = r'C:\TEST\GEODATABASE.mdb'
arcpy.env.workspace = workspace
edit = arcpy.da.Editor(arcpy.env.workspace)
class Net_Stands(object):
 """Implementation for Net_Stands_addin.button (Button)"""
 def __init__(self):
 self.enabled = True
 self.checked = False
 def onClick(self):
 try:
 self.mxd = arcpy.mapping.MapDocument('current')
 df = self.mxd.activeDataFrame
 layers = arcpy.mapping.ListLayers(self.mxd)
 Stands = arcpy.mapping.ListLayers(self.mxd, "StandsMaintenance", df)[0]
 CompanyRoads = arcpy.mapping.ListLayers(self.mxd, "roads", df)[0]
 layers = arcpy.mapping.ListLayers(self.mxd)
 #creates Stands layer as temp Stands_lyr
 arcpy.MakeFeatureLayer_management(Stands,"Stands_lyr")
 #defines layer as temp Stands_lyr
 Stands_temp = arcpy.mapping.Layer("Stands_lyr")
 #creates a NetStands_Temp to cut the roads_buffer out of
 arcpy.MakeFeatureLayer_management(Stands_temp, "NetStands_Temp")
 #defines layer as temp NetStands_Temp
 NetStands_Temp = arcpy.mapping.Layer("NetStands_Temp")
 #From the roads feature class in the mxd, it makes a temp TRACT_BOUNDARY in the mxd.
 arcpy.MakeFeatureLayer_management(CompanyRoads,"CompanyRoads_lyr")
 #defines layer as temp TRACTS_BOUNDARY
 CompanyRoads_temp = arcpy.mapping.Layer("CompanyRoads_lyr")
 #Calculates the buffer distance based on value in Row_Feet into meters
 arcpy.CalculateField_management(CompanyRoads_temp,"ROW_METERS_HALF","!ROW_FEET! * 0.1524","PYTHON_9.3","#")
 #creates the buffer layer based on CompanyRoads_temp
 mybuffer = arcpy.Buffer_analysis(CompanyRoads_temp,"#","ROW_METERS_HALF","FULL","ROUND","ALL","#")
 #Erases the area that from mybuffer that overlap with Stands_temp
 Erase_FC = arcpy.Erase_analysis(Stands_temp, mybuffer.getOutput(0))
 #Recalculates the polygon shape since the roads buffer has been erased
 arcpy.CalculateField_management(Erase_FC.getOutput(0),"NET_ACRES","!shape.area@acres!","PYTHON_9.3","#")
 #Marker, this is the last line of code that works.
 saysomething = pythonaddins.MessageBox("Completed Net_Acres Processing", "Clicked", 0)
 print saysomething
 #Start Editing
 edit.startEditing(True,False)
 edit.startOperation()
 try:
 joinfields = ['STID', 'NET_ACRES']
 joindict = {}
 with arcpy.da.SearchCursor(out_fc, joinfields) as rows:
 for row in rows:
 #joindict.append(row[0]),joindict.append(row[1])
 joinval = row[0]
 val1 = row[1]
 joindict[joinval]= [val1]
 print pythonaddins.MessageBox((row[0]) + " " + str(row[1]) + " joindict", "Joindict", 1)
 del row, rows
 with arcpy.da.UpdateCursor(Stands, joinfields) as recs:
 for rec in recs:
 keyval = rec[0]
 if joindict.has_key(keyval):
 print pythonaddins.MessageBox((rec[1]) + " rec" + str(rec[1]) + " rec"+ str(joindict[keyval]) + " joindict", "In the Update", 1)
 rec[1] = joindict[keyval]
 #print rec
 recs.updateRow(rec)
 del rec, recs
 except Exception, e:
 # If an error occurred, print line number and error message
 tb = sys.exc_info()[2]
 print pythonaddins.MessageBox("Line %i" % tb.tb_lineno)
 print pythonaddins.MessageBox(e.message)
 #print e.message
 exceptions = pythonaddins.MessageBox(arcpy.GetMessages())
 print exceptions
 edit.stopOperation()
 edit.stopEditing(True)
 print pythonaddins.MessageBox("complete")
 arcpy.RefreshActiveView()
 arcpy.RefreshTOC()
 arcpy.Delete_management(out_fc)
 arcpy.Delete_management(Erase_FC)
 arcpy.Delete_management(mybuffer_temp)
 code_errors = pythonaddins.MessageBox(arcpy.GetMessages(), "Code Errors", 1)
 print code_errors
 except arcpy.ExecuteError:
 lasterrors = pythonaddins.MessageBox(arcpy.GetMessages(), "WTF_Except Errors",1)
 print lasterrors
Evil Genius
6,2992 gold badges29 silver badges40 bronze badges
asked Jul 21, 2015 at 17:45
2
  • Can you verify that the cursor is actually reading data? i.e with arcpy.da.SearchCursor("out_fc", ["OBJECTID"]) as cursor: rows = {row[0] for row in cursor}, then print len(rows) Commented Jul 21, 2015 at 18:17
  • When executing this code in pyscripter, not as a Python Add-in, and with hard coded mxd, the da.SearchCursor on out_fc will return the proper values. However, for me to print the da.UpdateCursor, I must change the it to a da.SearchCursor and then take out the start/stop editing lines. This is because it will throw an error on the "for rec in recs" line, saying that edits can't be made in the session. So I haven't yet been able to fully execute the da.UpdateCursor, but the da.SearchCursor returns. Commented Jul 21, 2015 at 19:43

2 Answers 2

1

After many trials and tribulations, I have got a working code with many bells and whistles that I feel obliged to share since they are a culmination of many 'stolen' snippets obtained from this site. I have included comments in much of the script to describe what is going on, so hopefully that is helpful. Basically, I was able to get both a arcpy.UpdateCursor and a arcpy.da.UpdateCursor to work. One of the trickiest aspects was for the code to be able to edit a workspace that was dynamic, in that if they were direct editing a Versioned SDE or if they had 'Checked Out' their data from SDE and working locally. The code had to account for this, but not all of my wishes were granted. The code will kick back a message if the user is NOT in edit mode already ONLY when in SDE. If they are using a local GDB, then the code still runs, but the edits are essentially not written...The new numbers appear in the attribute table, however, if you 'Reload Cache', they revert back and the edits aren't saved regardless. If already the user has already initiated an edit session, the edits aren't saved until the user "Saves Edits". Furthermore, if the SDE connection is not the 'hard coded' SDE_versioned Connection I'm assuming, a DialogMessage will appear for them to choose the Database Connection which must match the intended lyr. If not, it will loop until they choose the correct one. Also, this is all done "in_memory" and is lightening fast when run on a local GDB. So here goes:

class NoFeatures(Exception):
 pass
import arcpy, traceback
import pythonaddins
import os, sys
import arcpy.toolbox
from arcpy import env
arcpy.env.overwriteOutput = True
#If you want to see the "in_memory/Layers being created, change below to "True"
#...then you must "#" comment out the Delete_Management("in_memory") at the bottom of code for them to persist in the TOC.
arcpy.env.addOutputsToMap = False
class Net_Stands(object):
 """Implementation for Net_Stands_addin.button (Button)"""
 def __init__(self):
 self.enabled = True
 self.checked = True
 def onClick(self):
 try:
 self.mxd = arcpy.mapping.MapDocument('current')
 df = self.mxd.activeDataFrame
 Stands = arcpy.mapping.ListLayers(self.mxd, "ForestStands", df)[0]
 layers = arcpy.mapping.ListLayers(self.mxd)
 #This will determine what the data connection is, either to a versioned SDE or Checkout GBD.
 fullPath = os.path.dirname(arcpy.Describe(Stands).catalogPath)
 try:
 # Create a Describe object for an SDE database
 try:
 #If newDir is a directory, then it is a GDB, if it is not a GDB, then the code will go to the 'else' where is connects with the SDE. This will not enable editing on .mdb
 if os.path.isdir(str(fullPath.rsplit('\\',1)[0])):
 workspace = os.path.dirname(fullPath)
 arcpy.env.workspace = workspace
 if arcpy.Exists(workspace):
 arcpy.Compact_management(workspace)
 else:
 pass
 #pythonaddins.MessageBox(arcpy.GetMessages(2) + "I see you have Checked-Out Data", "Messages",1)
 elif (str(fullPath.rsplit('\\',1)[1])) == "Hard-Coded Versioned SDE.sde":
 sdeconnect = "Database Connections\Whats in the ArcCatalog Connection.sde"
 workspace = sdeconnect
 arcpy.env.workspace = workspace
 else:
 loop = True
 while loop:
 paramWorkspace = pythonaddins.OpenDialog('Choose Database Connection',False, r'','Open')
 paramdesc = arcpy.Describe(paramWorkspace)
 if not (paramWorkspace == (str(fullPath.rsplit('\\',1)[0]))):
 pythonaddins.MessageBox("Please Choose Database Connections for ForestStands", "Database Connections", 1)
 loop = True
 continue
 else:
 pythonaddins.MessageBox("You Have Chosen Wisely", "Database Connections", 1)
 workspace = paramWorkspace
 arcpy.env.workspace = workspace
 loop = False
 break
 except:
 pass
 except:
 pythonaddins.MessageBox(arcpy.GetMessages(2) + " Failed to connect to " + fullPath + " Please Create a new Database Connection", "Connection Failure",1)
 sys.exit
 #Now that the connection has been made:
 try:
 fields = ("OBJECTID")
 #This da.UpdateCursor will cause an SDE Editor to fail if not already editing, thus initiating the "You are not in Editor" msg.
 #...you must be in Editor to allow for multiple da.UpdateCursor on a layer.
 newCur = arcpy.da.UpdateCursor(Stands, fields)
 with newCur as cursor:
 for row in cursor:
 break
 try:
 if arcpy.Exists(os.path.join(fullPath, "CompanyRoads")):
 #Retrieves CompanyRoads from Workspace of ForestStands if in a GDB
 fc = (os.path.join(fullPath,"CompanyRoads"))
 memoryRoads = "in_memory" + "\\" + "CompanyRoads"
 arcpy.CopyFeatures_management(fc, memoryRoads)
 memoryRoads_lyr = arcpy.mapping.Layer(memoryRoads)
 arcpy.RefreshActiveView
 arcpy.RefreshTOC
 elif arcpy.Exists("SDE_FeatureDataset.GISLOADER.CompanyRoads_Maintenance"):
 #Retrieves CompanyRoads_Maintenance from Workspace of ForestStands if in a SDE
 fc = ("SDE_FeatureDataset.GISLOADER.CompanyRoads_Maintenance")
 memoryRoads = "in_memory" + "\\" + "CompanyRoads"
 arcpy.CopyFeatures_management(fc, memoryRoads)
 memoryRoads_lyr = arcpy.mapping.Layer(memoryRoads)
 arcpy.RefreshActiveView
 arcpy.RefreshTOC
 #pythonaddins.MessageBox("memoryRoads", "Memory Roads",1)
 elif arcpy.Exists(paramdesc):
 #Retrieves CompanyRoads_Maintenance from Workspace choosen by user
 fc = (paramdesc + ".CompanyRoads")
 memoryRoads = "in_memory" + "\\" + "CompanyRoads"
 arcpy.CopyFeatures_management(fc, memoryRoads)
 memoryRoads_lyr = arcpy.mapping.Layer(memoryRoads)
 arcpy.RefreshActiveView
 arcpy.RefreshTOC
 else:
 #No Features Found
 raise NoFeatures()
 except NoFeatures:
 pythonaddins.MessageBox(arcpy.GetMessages(2) + " No CompanyRoads Found", "CompanyRoads lyr Missing",1)
 #This try/except on Editor will initiate an edit session.
 #...startEditing/startOperation must be present regardless if already in an edit session for da.UpdateCursor to work in SDE
 try:
 edit = arcpy.da.Editor(arcpy.env.workspace)
 # Start an edit session. Must provide the worksapce.
 # Edit session is started without an undo/redo stack for versioned data
 # (for second argument, use False for unversioned data)
 edit.startEditing(True, False)
 # Start an edit operation
 edit.startOperation()
 except:
 pythonaddins.MessageBox("Not in Edit Session" + arcpy.GetMessages(2), "Edit Errors", 1)
 pythonaddins.MessageBox(traceback.format_exc(), "Why Edits Failed", 1)
 else:
 try:
 #Rest of script...
 #
 #Creation of an identical lyr of ForestStands for geoprocessing
 memoryStands = "in_memory" + "\\" + "Stands_lyr"
 arcpy.CopyFeatures_management(Stands, memoryStands)
 #
 #This creates a buffer of roads of 50 meters and selects only roads that the buffer intersects with any stand.
 #...this drastically increases processing time since only a select set of raods are used for the calculation.
 arcpy.SelectLayerByLocation_management(memoryRoads_lyr, 'intersect', memoryStands, "50", "NEW_SELECTION")
 memoryNetStands_Temp = "in_memory" + "\\" + "NetStands_Temp"
 arcpy.CopyFeatures_management(Stands, memoryNetStands_Temp)
 memoryRoads_temp = "in_memory" + "\\" + "roads_lyr"
 arcpy.CopyFeatures_management(memoryRoads_lyr, memoryRoads_temp)
 arcpy.AddField_management(memoryRoads_temp, "ROW_METERS_FLOAT", "FLOAT")
 CursorFieldNames = ["ROW_FEET", "ROW_METERS_FLOAT"]
 cursor = arcpy.da.UpdateCursor(memoryRoads_temp,CursorFieldNames)
 for row in cursor:
 row[1] = row[0] * 0.1524 #Write area value to field
 cursor.updateRow(row)
 del row, cursor #Clean up cursor objects
 arcpy.Buffer_analysis(memoryRoads_temp,"in_memory/my_buffer","ROW_METERS_FLOAT","FULL","ROUND","ALL","#")
 arcpy.Erase_analysis(memoryStands, "in_memory/my_buffer", "in_memory/Erase_FC")
 #The spatial reference # can be found in Arcmap for reference Different Projection
 srNAD = arcpy.SpatialReference()
 #The spatial reference # can be found in Arcmap for reference NAD 1983 UTM Zone 17
 srUTM = arcpy.SpatialReference()
 #This try/except splits out the NorthEast data from the rest to calculate acres in UTM.
 try:
 row,rows,cursor= None,None,None
 #Notice NOT an da.UpdateCursor, just a UpdateCursor, this is bc all fields are required including SHAPE.
 cur = arcpy.UpdateCursor("in_memory/Erase_FC", ["FIELD_AREA"],srUTM)
 for row in cur:
 #Use the FIELD_AREA field to meet the conditional statement.
 if row.FIELD_AREA == "NorthEast":
 row.NET_ACRES = round((row.SHAPE.area * 0.000247104393),3)
 cur.updateRow(row)
 else:
 pass
 curNAD = arcpy.UpdateCursor("in_memory/Erase_FC", ["FIELD_AREA"], srNAD)
 for row in curNAD:
 #Use the FIELD_AREA field to meet the conditional statement.
 if row.FIELD_AREA <> "NorthEast":
 row.NET_ACRES = round((row.SHAPE.area * 0.000247104393),3)
 curNAD.updateRow(row)
 except:
 pythonaddins.MessageBox(traceback.format_exc(), "Try Errors",1)
 pythonaddins.MessageBox(arcpy.GetMessages(2), "MSG",1)
 #If ever in doubt, the line below also works for the try/except above, without splitting for UTM/NAD
 #arcpy.CalculateField_management("in_memory/Erase_FC","NET_ACRES","!shape.area@acres!","PYTHON_9.3","#")
 #The CROWN JEWEL of the code...try/except on creating a 'data dictionary from the "in_memory/Erase_FC" NET_ACRES field to populate
 #...the ForestStands NET_ACRES field. The da.SearchCursor grabs the newly derived values from the "in_memory/Erase_FC".
 #...Using the da.SearchCursor is effecient since it only grabs the fields you require, without grabing all the other fields that tax performance
 try:
 row= None
 # Create Dictionary
 with arcpy.da.SearchCursor("in_memory/Erase_FC",["StID", "NET_ACRES"]) as srows:
 path_dict = dict([srow[0], srow[1]] for srow in srows)
 #pythonaddins.MessageBox(str(path_dict), "Path_dictionary",1)
 area = 'NorthEast'
 #queryfield = 'AREA'
 whereclauseUTM = "FIELD_AREA = '%s'" % area
 #query = "[" + queryfield + "] " + "= " + "\'" + area + "\'" # else create a new query
 with arcpy.da.UpdateCursor(Stands,["StID", "NET_ACRES", "GROSS_ACRES","SHAPE@AREA", " FIELD_AREA"], whereclauseUTM,srUTM) as urows:
 for row in urows:
 row[2] = round((row[3] * 0.000247104393),3)
 if row[0] in path_dict:
 row[1] = path_dict[row[0]]
 #urows.updateRow(row)
 else:
 row[1] = 0
 urows.updateRow(row)
 whereclauseNAD = " SD_AREA <> '%s'" % area
 with arcpy.da.UpdateCursor(Stands,["StID", "NET_ACRES", "GROSS_ACRES","SHAPE@AREA", " SD_AREA"], whereclauseNAD,srNAD) as urows:
 for row in urows:
 row[2] = round((row[3] * 0.000247104393),3)
 if row[0] in path_dict:
 row[1] = path_dict[row[0]]
 else:
 row[1] = 0
 urows.updateRow(row)
 except:
 edit.undoOperation()
 pythonaddins.MessageBox(arcpy.GetMessages(2), "Undo Operation", 1)
 pythonaddins.MessageBox(traceback.format_exc(), "Traceback on Excecute Error",1)
 else:
 # Stop the edit session and DONT save the changes in code. Requires USER to save changes.
 edit.stopOperation()
 #edit.stopEditing(True)
 arcpy.RefreshActiveView()
 arcpy.RefreshTOC()
 except:
 #If these are triggered, then edits aren't saved, and the faulty lines of code are identified. No HARM, No Faul.
 pythonaddins.MessageBox(arcpy.ExecuteError(), "Execute Error",1)
 pythonaddins.MessageBox(traceback.format_exc(), "Traceback on Excecute Error",1)
 pythonaddins.MessageBox(arcpy.GetMessages(2), "Error MSG",1)
 except:
 pythonaddins.MessageBox(arcpy.GetMessages(2) + " You are not in an Edit Session", "Cancelled Operation", 0)
 pythonaddins.MessageBox(traceback.format_exc(), "Traceback",1)
 except:
 pythonaddins.MessageBox(arcpy.GetMessages(2), "Except Errors",1)
 pythonaddins.MessageBox(traceback.format_exc(), "TraceBack", 1)
 finally:
 arcpy.Delete_management("in_memory")
 if os.path.isdir(str(fullPath.rsplit('\\',1)[0])) and arcpy.Exists(workspace):
 arcpy.Compact_management(workspace)
 del workspace, fullPath
 arcpy.RefreshTOC
 arcpy.RefreshActiveView
answered Aug 7, 2015 at 14:24
1

From my experience it seems that the syntax "with arcpy.da.UpdateCcursor" (ie the use of a Python "with" statement for a cursor) in the code of a Python Add-In causes problems. I didn't check if using a arcpy.da cursor without a "with" statement works. The following code example use a "normal" UpdateCursor (arcpy.UdpateCursor, not part of the arcpy.da family) and should work. It's part of a Python Add-In that works on ArcGis 10.2.2 and Windows 7.

cursorYes = arcpy.UpdateCursor(saisie_layer)
for aline in cursorYes :
 IDY = aline.getValue(IDfield)
 if IDY not in list_aVerif_LA4 :
 aline.setValue("TFV", "FF1G01-01")
 aline.setValue("THEME", "11")
 aline.setValue("ORI", None)
 correction = aline.getValue("Type_modification")
 cursorYes.updateRow(aline)
 if IDY in list_aVerif_FF :
 aline.setValue("Type_modification", "correction_tfv")
 aline.setValue("TFV", "FF1G01-01")
 aline.setValue("THEME", "11")
 aline.setValue("ORI", None)
 cursorYes.updateRow(aline)
# delete cursor
del aline
del cursorYes

To summarize try to use old fashion cursors : arcpy.UpdateCursor and do not forget to delete the cursor object and the variable used to store each row of the cursor.

Finally I would like to precise that any attempt to use a cursor outside the button class of a Python Add-In failed. Our Python Add-In has many button with the same Python code, the only difference being the value they write in a particular field. We tried to make a function outside the button classes to avoid repeating the same lines for each button, but calling a function from the button class resulted in the arcpy.UpdateCursor not working.

PolyGeo
65.5k29 gold badges115 silver badges349 bronze badges
answered Aug 7, 2015 at 11:46

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.