Select to view content in your preferred language

User Authentication in hosted .pyt toolbox / geoprocessing tool

249
3
3 weeks ago
BlakeFulkerson
New Contributor

Hello everyone,

I have a .pyt toolbox which runs correctly in the ArcGIS Pro 3.5 environment but fails when the tool is hosted on ArcGIS Enterprise 11.3.  The tool uses arcpy and the arcgis api for python to retrieve a hosted webmap, included layers and hosted layout, all of which are available to all users within the organization.

The tool uses gis = GIS("home") when run inside of ArcGIS Pro.  However,  when run in a hosted environment, I receive the the following errors:

{"error":{"code":499,"message":"Token Required","details":[]}}

RuntimeError: Failed to create layer from service at https://portal_url.com/gisweb/rest/services/Hosted/feature_layer/FeatureServer/1.\n"

I assume this is related to user authentication/credentials, but I cannot figure out how to properly pass credentials when hosting.  Should I be using keyring to do so?  If so, what is the proper way of setting it up?  Does the .pyt toolbox need to be run from the server machine in ArcGIS Pro, or can it be run on my local computer?

Below is the execute() portion of the .pyt toolbox.  Any insights you may have into how to properly pass API credentials when hosting a .pyt toolbox on enterprise would be greatly appreciated.

"""   
    def execute(self, parameters, messages):

        parcel_apn = parameters[0].valueAsText
        output_pdf = parameters[1].valueAsText

        web_map_id = "a4373271ba30412939sh23201h8h282"
        layout_id = "btheh82hek930jj3932429je9302030"

        attributes = ["SHAPE@", "apn", "property_name"]

        parcels = [parcel_apn]

        gis = GIS("home")
        

        webmap_item = gis.content.get(web_map_id)
        arcpy.AddMessage(webmap_item)
        web_map_json = webmap_item.get_data()
        arcpy.AddMessage("")
        arcpy.AddMessage("")
        pretty_json = json.dumps(web_map_json, indent=4)
        arcpy.AddMessage(pretty_json)
        web_map_json.update(json.loads('''{"mapOptions" : { "extent" : {  "xmin":6787761,  "ymin":2073010,  "xmax":6787762,  "ymax":2073011,  "spatialReference" : {   "wkid" : 6418  } }}}'''))

        pagx_path = os.path.join(arcpy.env.scratchFolder, "Report.pagx")
        layout_item = gis.content.get(layout_id)
        layout_item.download(pagx_path)

        result = arcpy.mp.ConvertWebMapToArcGISProject(json.dumps(web_map_json), template_pagx=fr"{pagx_path}\Property_Report_1.pagx")
        
        aprx = result.ArcGISProject

        map = aprx.listMaps("Web Map")[0]

        layer = map.listLayers("Properties")[0]

        layout = aprx.listLayouts("Property Report")[0]

        map_frame = layout.listElements("MAPFRAME_ELEMENT", "Property Map Frame")[0]
        arcpy.AddMessage(map_frame.name)

        property_name_text = layout.listElements("TEXT_ELEMENT", "Property Name")[0]
        apn_text = layout.listElements("TEXT_ELEMENT", "APN")[0]

        for parcel in parcels:
            
            arcpy.management.SelectLayerByAttribute(layer, "NEW_SELECTION", where_clause=f"apn = '{parcel}'") 
            
            with arcpy.da.SearchCursor(layer, attributes, where_clause=f"apn = '{parcel}'") as cursor:
                for row in cursor:
                    extent = row[0].extent
                    apn = row[1]
                    property_name = row[2]

            # Get Parcel Layer Extent and Pad it by 20-Percent in the Active Map Frame

            pad = 0.20
            
            width_pad = extent.width * pad
            height_pad = extent.height * pad
            
            new_xmin = (extent.XMin - width_pad)
            new_xmax = (extent.XMax + width_pad)
            new_ymin = (extent.YMin - height_pad)
            new_ymax = (extent.YMax + height_pad)
            
            padded_extent = arcpy.Extent(new_xmin, new_ymin, new_xmax, new_ymax, spatial_reference=extent.spatialReference)

            map_frame.camera.setExtent(padded_extent)

            property_name_text.text = f"{property_name}"
            apn_text.text = f"APN: {apn}"
            layer.definitionQuery = f"APN = '{parcel}'"

            layout.exportToPDF(output_pdf)
            return
"""
0 Kudos
3 Replies
BenCapell_NWTF10
Occasional Contributor

When you run this in ArcGIS Pro Geoprocessing pane the toolbox script is pulling your credentials from your ArcGIS Pro login by using: gis = GIS("home") . I'm not familiar with the login functionality on the Enterprise side when passing "home". You may need to enter user credentials to run in enterprise: https://developers.arcgis.com/python/latest/guide/working-with-different-authentication-schemes/#:~:...

Do you happen to have MFA setup in your Enterprise?

0 Kudos
BlakeFulkerson
New Contributor

Hi Ben, we do not have MFA setup in our Enterprise.

0 Kudos
BenCapell_NWTF10
Occasional Contributor

Ok

@BlakeFulkerson does the code at the link below work for you after customizing to make your portal url, username, and password?

https://developers.arcgis.com/python/latest/guide/working-with-different-authentication-schemes/#:~:...)

0 Kudos