I'm attempting to use the addResources operation to upload JSON confiuguration files (e.g., config.json, image-resources-list.json, etc.) to a programatically-created ExB app, and keep running into issues.
I've tried sending the JSON body in the Text parameter, sending the JSON body in the File parameter, sending as binary in the File parameter, all manner of encoding, and nothing works. I usually get a "Success" response from the addResources call, but when I attempt to list the item resources, they're empty.
I feel like I'm missing something obvious. Here's one version of my Python code:
import arcgis
from arcgis import GIS
import requests
gis = GIS('https://arcgis.com', username, password)
web_experience_item = gis.content.add(
{'type': 'Web Experience',
'title': 'New Experience',
'tags':'web experience'})
url = f"https://www.arcgis.com/sharing/rest/content/users/{username}/items/{web_experience_item.id}/addResources"
with open(f"{tempdir}//images//icon-resources-list.json", 'rb') as f:
data = {
"file": f, # also tried "text", also tried encoded json string
"fileName": "icon-resources-list.json",
"resourcesPrefix": "images",
"access": "inherit",
"token": token,
"f": "json"}
response = requests.post(url, data=data)
print(response.json()) # returns success
with open(f"{tempdir}//images//image-resources-list.json", 'rb') as f:
data = {
"file": f, # also tried "text", also tried encoded json string
"fileName": "image-resources-list.json",
"resourcesPrefix": "images",
"access": "inherit",
"token": token,
"f": "json"}
response = requests.post(url, data=data)
print(response.json()) # returns success
with open(f"{tempdir}//config//config.json", 'rb') as f:
data = {
"file": f, # also tried "text", also tried encoded json string
"fileName": "config.json",
"resourcesPrefix": "config",
"access": "inherit",
"token": token,
"f": "json"}
response = requests.post(url, data=data)
print(response.json()) # returns success
exb_item = gis.content.get(web_experience_id)
item_resources = exb_item.resources.list()
print(exb_resources) # empty list
response = requests.get(f"https://orgname.maps.arcgis.com/sharing/rest/content/items/{web_experience_id}/resources/config/config.json?f=json&token={token}")
print(response.json()) # 404 error because no resources exist
Solved! Go to Solution.
I figured it out. The request to add the item resources should look like this:
with open(f"{tempdir}\\images\\image-resources-list.json", 'r') as f:
data = {
"fileName": "image-resources-list.json",
"resourcesPrefix": "images",
"access": "inherit",
"token": token,
"f": "json",
}
response = requests.post(url, data=data, files={"file": f})
print(response.json())
Note the addition of the files parameter with a dictionary to define the item resource to be added.
Hi Joseph,
I've been able to do it like this. The _encode_files method is a an slightly modified version of the method in requests.models
import requests
import urllib3.filepost as url_filepost
import magic
params = {
"fileName": "icon-resources-list.json",
"resourcesPrefix": "images",
"access": "inherit",
"token": token,
"f": "json"}
file_contents = open(resource_file_name, "rb").read()
body, content_type = _encode_files(params, file_contents)
response = requests.post(url, data=body, headers={"Content-Type": content_type})
def _encode_files(data, files):
"""Build the body for a multipart/form-data request.
Will successfully encode files when passed as a dict or a list of
tuples. Order is retained if data is a list of tuples but arbitrary
if parameters are supplied as a dict.
The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
or 4-tuples (filename, fileobj, contentype, custom_headers).
"""
if not files:
raise ValueError("Files must be provided.")
if isinstance(data, str):
raise ValueError("Data must not be a string.")
new_fields = []
fields = list((data or {}).items())
files = list((files or {}).items())
for field, val in fields:
if isinstance(val, str) or isinstance(val, bytes) or not hasattr(val, '__iter__'):
val = [val]
for v in val:
if v is not None:
# Don't call str() on bytestrings: in Py3 it all goes wrong.
if not isinstance(v, bytes):
v = str(v)
new_fields.append(
(field.decode('utf-8') if isinstance(field, bytes) else field,
v.encode('utf-8') if isinstance(v, str) else v))
for (k, v) in files:
# support for explicit filename
ft = None
fh = None
content_type = "image/png"
if isinstance(v, (tuple, list)):
if len(v) == 2:
fn, fp = v
elif len(v) == 3:
fn, fp, ft = v
else:
fn, fp, ft, fh = v
magik = magic.Magic(mime=True)
content_type = magik.from_file(fn)
fn = os.path.basename(fn)
else:
fn = k + ".png" # If nothing specified expect png image
fp = v
if isinstance(fp, (str, bytes, bytearray)):
fdata = fp
else:
fdata = fp.read()
rf = url_filepost.RequestField(name=k, data=fdata, filename=fn, headers=fh)
rf.make_multipart(content_type=content_type)
new_fields.append(rf)
body, content_type = url_filepost.encode_multipart_formdata(new_fields)
return body, content_type
Hope this helps you.
Robert
I figured it out. The request to add the item resources should look like this:
with open(f"{tempdir}\\images\\image-resources-list.json", 'r') as f:
data = {
"fileName": "image-resources-list.json",
"resourcesPrefix": "images",
"access": "inherit",
"token": token,
"f": "json",
}
response = requests.post(url, data=data, files={"file": f})
print(response.json())
Note the addition of the files parameter with a dictionary to define the item resource to be added.