You are correct that it requires a start/stop editing process in arcpy. I found this to be annoying, so I created some wrapper classes to automatically stop and start the edit sessions for cursors. I made one for Update and Insert Cursors that make use of the __enter__ and __exit__ methods to handle the edit session initializing and closing (must be used with a "with" statement). These make it much more convenient:
import arcpy
import os
class WSMixin(object):
@staticmethod
def find_ws(path, ws_type='', return_type=False):
"""finds a valid workspace path for an arcpy.da.Editor() Session
Required:
path -- path to features or workspace
Optional:
ws_type -- option to find specific workspace type (FileSystem|LocalDatabase|RemoteDatabase)
return_type -- option to return workspace type as well. If this option is selected, a tuple
of the full workspace path and type are returned
"""
def find_existing(path):
if arcpy.Exists(path):
return path
else:
if not arcpy.Exists(path):
return find_existing(os.path.dirname(path))
if isinstance(path, (arcpy.mapping.Layer, arcpy.mapping.TableView)):
path = path.dataSource
if os.sep not in str(path):
if hasattr(path, 'dataSource'):
path = path.dataSource
else:
path = arcpy.Describe(path).catalogPath
path = find_existing(path)
desc = arcpy.Describe(path)
if hasattr(desc, 'workspaceType'):
if ws_type == desc.workspaceType:
if return_type:
return (path, desc.workspaceType)
else:
return path
else:
if return_type:
return (path, desc.workspaceType)
else:
return path
path = str(path)
SPLIT = filter(None, str(path).split(os.sep))
if path.startswith('\\\\'):
SPLIT[0] = r'\\{0}'.format(SPLIT[0])
for i in xrange(1, len(SPLIT)):
sub_dir = os.sep.join(SPLIT[:-i])
desc = arcpy.Describe(sub_dir)
if hasattr(desc, 'workspaceType'):
if ws_type == desc.workspaceType:
if return_type:
return (sub_dir, desc.workspaceType)
else:
return sub_dir
else:
if return_type:
return (sub_dir, desc.workspaceType)
else:
return sub_dir
class UpdateCursor(WSMixin):
"""wrapper clas for arcpy.da.UpdateCursor, to automatically
implement editing (required for versioned data, and data with
geometric networks, topologies, network datasets, and relationship
classes"""
def __init__(self, *args, **kwargs):
"""initiate wrapper class for update cursor. Supported args:
in_table, field_names, where_clause=None, spatial_reference=None,
explode_to_points=False, sql_clause=(None, None)
"""
self.args = args
self.kwargs = kwargs
self.edit = None
self.alreadyInEditSession = False
def __enter__(self):
ws = None
if self.args:
ws = self.find_ws(self.args[0])
elif 'in_table' in self.kwargs:
ws = self.find_ws(self.kwargs['in_table'])
try:
self.edit = arcpy.da.Editor(ws)
self.edit.startEditing()
self.edit.startOperation()
self.cursor = arcpy.da.UpdateCursor(*self.args, **self.kwargs)
return self.cursor
except Exception as e:
if isinstance(e, RuntimeError) and e.message == 'start edit session':
self.cursor = arcpy.da.UpdateCursor(*self.args, **self.kwargs)
self.alreadyInEditSession = True
return self.cursor
else:
raise e
def __exit__(self, type, value, traceback):
if not self.alreadyInEditSession:
try:
self.edit.stopOperation()
self.edit.stopEditing(True)
except:
pass
self.edit = None
try:
del self.cursor
except:
pass
class InsertCursor(WSMixin):
"""wrapper clas for arcpy.da.InsertCursor, to automatically
implement editing (required for versioned data, and data with
geometric networks, topologies, network datasets, and relationship
classes"""
def __init__(self, *args, **kwargs):
"""initiate wrapper class for update cursor. Supported args:
in_table, field_names
"""
self.args = args
self.kwargs = kwargs
self.edit = None
self.alreadyInEditSession = False
def __enter__(self):
ws = None
if self.args:
ws = self.find_ws(self.args[0])
elif 'in_table' in self.kwargs:
ws = self.find_ws(self.kwargs['in_table'])
try:
self.edit = arcpy.da.Editor(ws)
self.edit.startEditing()
self.edit.startOperation()
self.cursor = arcpy.da.InsertCursor(*self.args, **self.kwargs)
return self.cursor
except Exception as e:
if isinstance(e, RuntimeError) and e.message == 'start edit session':
self.cursor = arcpy.da.InsertCursor(*self.args, **self.kwargs)
self.alreadyInEditSession = True
return self.cursor
else:
raise e
def __exit__(self, type, value, traceback):
if not self.alreadyInEditSession:
try:
self.edit.stopOperation()
self.edit.stopEditing(True)
except:
pass
self.edit = None
try:
del self.cursor
except:
pass
I have named the above module as "cursors.py". With this, you can simply do the following:
import cursors
fc = r'<some sde feature class>'
fields = ['Field1', 'Field2', ...]
with cursors.UpdateCursor(fc, fields) as rows:
for r in rows:
rows.updateRow(r)
This saves me a bunch of redundant code, so I hope others find it useful as well. This code will also catch the error thrown if an edit session is already started, if so, you could revert to normal cursors if already in an edit session.