Using addResources to upload json files for ExB app

1020
3
Jump to solution
01-11-2023 01:47 PM
JosephRhodes2
Occasional Contributor II

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

 

 

 

 

0 Kudos
1 Solution

Accepted Solutions
JosephRhodes2
Occasional Contributor II

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.

View solution in original post

3 Replies
Robert_van_Gilst
New Contributor III

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

JosephRhodes2
Occasional Contributor II

Thanks Robert. I ended up solving the issue, but your response helped point me in the right direction.

JosephRhodes2
Occasional Contributor II

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.