Sometimes it is necessary to be able to manage attachments in some way, in this article we will cover certain concepts and later on my github there will be several examples that you can modify to your liking.<\/P>
To start, to be able to access the services of ArcGIS Enterprise (Portal) or ArcGIS Online the arcgis library is required, so if you do not have it installed you must do so (it comes included when installing ArcGIS Pro or within the Python of ArcGIS Enterprise).<\/P>
This first section will allow you to access your Portal or ArcGIS Online<\/P>
#Connect to the Portal
PORTAL = GIS("url", "user","password")<\/P>
Depending on what you are doing it may be necessary to obtain the token, this is basically used when you want to see the url of the attachments and the service is not public.<\/P>
token = PORTAL._con.token<\/P>
After this we can already access the service, the arcgis api allows access to the objects stored in the Portal or ArcGIS Online, for this from our site we will access the service address (it is the one with the yellow icon with red). (It is a text so it must go in quotes)<\/P>
service = PORTAL.content.get('item id')<\/P>
Then when we access this part we can see the different layers and tables that the service we are querying has, therefore it is necessary to place the number that indicates the layer or table index, and here I must clarify, that if we have for example: a geographic entity and a table both will have index 0, this is because the system detects as a list of layers and a list of tables within the service, therefore, in case there is a layer and a table both have index 0. Now if there are two layers and one table, there will be one layer with index 0, the other with 1 and the table will be 0. I hope not to cause confusion with this hehe.<\/P>
The thing is that to parameterize I did the following:<\/P>
num_layer=int(index)<\/P>
Note: it is a numeric data so you simply write the value<\/P>
As I indicated earlier there may be layers or tables, in this way, it may also be that attachments are found in a layer or a table, so I found it necessary to add something like this to the tool:<\/P>
type="here write layer or table"
if type=="layer":
layer=(service.layers)[num_layer]
elif type=="table":
layer =(service.tables)[num_layer]<\/P>
To build the URL, Esri's own API tells us how to get it, only it does not do it as a text but as an object, so I did the following using python's re library:<\/P>
cap_url=str(re.findall('htt.*'+str(num_layer), str(layer)))
cap_url=str(cap_url)[2:-2]<\/P>
In this way it transforms the object into a string (text)<\/P>
Now, we must identify the folder where we are going to save the attachments
folder=r"desired folder path"<\/P>
Another parameter I use is an empty text field where I will store the address where attachments are stored, so it can be parameterized with pandas dataframe and exactly what we will do here:<\/P>
sdf = layer.query(where="field name"+" IS NULL").sdf<\/P>
In this way the query will go only towards those who have that field empty<\/P>
After this an empty list is generated that will capture the records that we are going to include within an edition, this will be very important later since it allows entering data into the empty list and sending them to the editing method, according to what we are doing.<\/P>
elements_to_update = []<\/P>
Now yes we must iterate over the layer or table that has attachments<\/P>
#Iterate through dataframe
for index, row in sdf.iterrows():
objID=str("field containing objectid")
Attachments = layer.attachments.get_list(oid=objID)<\/P>
Within the same iteration I placed an if, as you can see in previous lines a list is created in Attachments, therefore, one characteristic of lists is that we can count the number of elements and precisely that is what if does, since we will query only where this list is greater than 0, thus we will be doubly sure that we will work only with data that has attachments.<\/P>
if len(Attachments)>0:<\/P>
Within this condition we are going to iterate over attachments corresponding to each ObjectId of main layer:<\/P>
for k in range(len(Attachments)):
attachmentId = Attachments[k]['id']
attachmentName = Attachments[k]['name']
img_url = cap_url+"\/{0}\/attachments\/{1}".format(objID,attachmentId)+"?token="+token
fileName = os.path.join(folder, attachmentName)<\/P>
As you can see, attachment Id and name are obtained, with this I concatenate previously obtained url with objectid of layer or table and attachment Id. This way a unique URL for each attachment is obtained.<\/P>
Also with this it is possible to concatenate folder path previously had and file name that attachment has.<\/P>
With this you can play a little, in this case I am going to show you something so system knows if an attachment with same name has already been downloaded in folder you are indicating, therefore it would not download that attachment. But it is also possible to tell it to create a folder for each element and download all attachments corresponding to that record there. In this case I will make it create file, another time I show you how to create folder, which by the way is found in script of download attachments from github.<\/A><\/P>Well as I said here part to check if attachment exists:<\/P>
file_exists=os.path.exists(fileName)
if file_exists is True:
arcpy.AddMessage("Do not download")
else:
arcpy.AddMessage("Download ShapeFile "+attachmentName)<\/P>
As you can see I use os and arcpy libraries here, os one is to check existence or not of file in folder indicated and arcpy in this case is to return message inside ArcGIS, which by the way if you don't use it you can replace arcpy.AddMessage here by print.<\/P>
Now already with URL we can download attachments<\/P>
try:
arcpy.AddMessage("Getting ShapeFile URL")
request=urllib.request.urlretrieve(img_url)
req = requests.get(img_url)
file = open(fileName, 'wb')
for chunk in req.iter_content(100000):
file.write(chunk)
file.close()
arcpy.AddMessage("ShapeFile "+attachmentName+" downloaded correctly in folder "+folder)<\/P>
Here deletes attachment from layer
capa.attachments.delete(objID,attachmentId)<\/p>
Here we will capture error to show it in system (in my case I was downloading shapefiles hence message says that.
except urllib.error.HTTPError as err:
 arcpy.AddMessage("Error " + str(err.code) + " no ShapeFile exists for download or there is a connection problem")
At this moment, and within indentation of if (where we were requesting it to return where the list had more than 0 elements) we will use the method that will capture the data and append it to our empty list.<\/P>
#Update the folder path field
selection=layer.query(where="objectid field"+"="+objID)
element_to_update=(selection.features[0])
element_to_update.attributes["field name"]=str(fileName)<\/P>
Now we will append the records we are capturing that have attachments and the field that will contain the empty path
elements_to_update.append(element_to_update)<\/P>
After appending these data to the empty list, with this method, we will proceed to use the list to edit the elements. This is done outside of the dataframe iteration, that is, it is outside everything.<\/P>
layer.edit_features(updates=elements_to_update)<\/P>
As you can see, updates indicates that it is equal to the empty list that was indicated earlier.<\/P>
And that's all, I have several examples in my
GitHub<\/A>, in these I download attachments and delete them from the service, or simply download them, or in another case, download attachments that are shapefiles and attach them to another service as new entities, I would be happy to explain how they work later.<\/P>Another very important thing I must clarify is that the toolbox attached is for version 3 onwards, if you have a version earlier than 2.9, then you have to configure the toolbox, that is why I also attach the scripts so you can customize it. I leave here a
link<\/A> which is a small guide to configure tools with versions lower than 2.9<\/STRONG><\/P>I hope it helps you<\/P>
<\/P>