Python API: FeatureLayerCollection Manager, add_to_definition and the json_dict

3712
5
07-22-2019 08:30 AM
JohnEvans6
New Contributor III

I am having trouble determining the proper way to use add_to_definition:

https://esri.github.io/arcgis-python-api/apidoc/html/arcgis.features.managers.html

A sample js dict seems to be here:

https://developers.arcgis.com/rest/services-reference/add-to-definition-feature-service-.htm

This may end up me not being able to determine the appropriate workflow. When I used ArcGIS Pro to publish my feature service the first time, the feature layers were published under one service definition (Map):

Service Definition

Feature Service

I am able to update layers using layer.edit_features, and remove them using featureLayerCollection.manager.delete_from_definition(json_delete_dict).

What I am trying to do is write a json_dict directly to the 'Map' Service Definition. I don't want to publish something separately via item.publish (unless I'm supposed to?):

https://esri.github.io/arcgis-python-api/apidoc/html/arcgis.gis.toc.html?highlight=publish#arcgis.gi...

I want to directly 'append', for lack of a better word, the layer to the service definition in the same way the other layers and data were published the first time:

Service Definition Layer

Is it possible to directly add a layer to a service definition without publishing the item first? Am I off base? If I'm not even close, can someone walk me through the steps to add a layer to a service definition? Is there sample code anywhere (specifically a python add_to_definition json dict)?

Appreciate any and all insight.

0 Kudos
5 Replies
WightmanAdmin
New Contributor III

I recently got something to work, and it may be of use to you. It uses the requests Python library instead of  the ArcGIS Python API (I couldn't get that to work either). I just POST to the addToDefinition tool and it adds a table. Looking through this thread helped me quite a bit. Still working on adding a relationship.

Of course, change type to "Feature Layer" if that's what you want, I've only gotten a table working.

from arcgis.gis import GIS
from arcgis.features import FeatureLayerCollection
import json
import requests

gis = GIS('https://www.arcgis.com','user','pw')

feature_item = gis.content.get('ITEMID')
flayer = FeatureLayerCollection.fromitem(feature_item)   
token = gis._con.token
url = f'{flayer.service.url}/AddToDefinition?token={token}' 
print(url)

add_json = ''' {
"layers" : [
{
"name" : "Maintenance_py",
"type" : "Table",
"objectIdField" : "FID",
"fields" : [
{
"name" : "reference",
"type" : "esriFieldTypeGUID",
"alias" : "reference",
"sqlType" : "sqlTypeOther",
"nullable" : false,
"editable" : true,
"length" : 38,
"visible" : true,
"domain" : null,
"defaultValue" : null
},
{
"name" : "MaintDate",
"type" : "esriFieldTypeDate",
"alias" : "Maintenance Date",
"sqlType" : "sqlTypeOther",
"length" : 8,
"nullable" : true,
"editable" : true,
"domain" : null,
"defaultValue" : null
},
{
"name" : "MaintType",
"type" : "esriFieldTypeString",
"actualType" : "nvarchar",
"alias" : "Maintenance Type",
"sqlType" : "sqlTypeNVarchar",
"length" : 256,
"nullable" : true,
"editable" : true,
"visible" : true,
"domain" : null,
"defaultValue" : null
},
{
"name" : "Notes",
"type" : "esriFieldTypeString",
"actualType" : "nvarchar",
"alias" : "Notes",
"sqlType" : "sqlTypeNVarchar",
"length" : 256,
"nullable" : true,
"editable" : true,
"visible" : true,
"domain" : null,
"defaultValue" : null
},
{
"name" : "CreationDate",
"type" : "esriFieldTypeDate",
"alias" : "CreationDate",
"sqlType" : "sqlTypeOther",
"length" : 8,
"nullable" : true,
"editable" : false,
"domain" : null,
"defaultValue" : null
},
{
"name" : "Creator",
"type" : "esriFieldTypeString",
"alias" : "Creator",
"sqlType" : "sqlTypeOther",
"length" : 50,
"nullable" : true,
"editable" : false,
"domain" : null,
"defaultValue" : null
},
{
"name" : "EditDate",
"type" : "esriFieldTypeDate",
"alias" : "EditDate",
"sqlType" : "sqlTypeOther",
"length" : 8,
"nullable" : true,
"editable" : false,
"domain" : null,
"defaultValue" : null
},
{
"name" : "Editor",
"type" : "esriFieldTypeString",
"alias" : "Editor",
"sqlType" : "sqlTypeOther",
"length" : 50,
"nullable" : true,
"editable" : false,
"domain" : null,
"defaultValue" : null
},
{
"name" : "FID",
"type" : "esriFieldTypeOID",
"actualType" : "int",
"alias" : "FID",
"sqlType" : "sqlTypeInteger",
"length" : 4,
"nullable" : false,
"editable" : false,
"domain" : null,
"defaultValue" : null
}
]
}
]
}'''

payload = {
        'addToDefinition': add_json,
        'async': 'false',
        'f': 'json',
        'token': token
}
session = requests.Session()
result = session.post(url,data=payload)
print(result)
JohnEvans6
New Contributor III

Hi Ryan appreciate the snippet. I could not figure out how to get a table added and this seems to do just that. I've got to wrap up another project then I'm diving back into this one. Thanks!

0 Kudos
WightmanAdmin
New Contributor III

I made more progress. I want to change this script so it actually uses the add_to_definition method from the API (since it would probably make my script less verbose), but for now, this works. Below is a modified of my final script (because it's doing something slightly different than what you want), but this snippet might be helpful too.

I haven't tested this exact script, but it should work. Just input the service URL of your table (it has to uploaded to AGOL first, doesn't matter where it is) and the item ID of where you want to add the table. Again, haven't tested this for a feature layer instead of a table.

EDIT: test this on some dummy data first. I've had addToDefinition go wrong before and pretty much delete all my data (but it was okay b/c it was dummy data). The finalized script shouldn't do that, but still test it.

from arcgis.gis import GIS
from arcgis.features import FeatureLayerCollection, FeatureLayer
import json, requests

url = 'https://www.arcgis.com'
user = 'username'
pw = 'pw'
gis = GIS(url,user,pw)
    
def build_json(table, name):
    # turn PropertyMap type to list of dictionaries (PropertyMap is not JSON
    # serializable)
    fields = []
    for i in table:
        d = {}
        for k,v in i.items():
            d[k] = v
        fields.append(d)
    
    # assumes field OBJECTID is your objectIdField
    temp_json = json.dumps({'layers':
        [{'name':name,
          'type': 'Table',
          'objectIdField': 'OBJECTID',
          'fields': fields}]})
    
    return temp_json   

def add_to_definition(origin,add_json,table_name):
    token = gis._con.token
    flayer = FeatureLayerCollection.fromitem(origin)
    url = f'{flayer.service.url}/AddToDefinition?token={token}' # build URL to POST to
    payload_add = {
            'addToDefinition': add_json,
            'async': 'true',
            'f': 'json',
            'token': token
    }
    session = requests.Session()
    result = session.post(url,data=payload_add)
    print(result)

def main():
    ago_table = FeatureLayer('URL to your feature layer')
    origin = gis.content.get('ID of the feature class you want to add data to')
    table_name = ago_table.properties.name
    
    fields = ago_table.properties.fields
    add_json = build_json(fields,table_name)
    
    add_to_definition(origin, add_json, table_name)

if __name__ == '__main__':
    main()‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
EarlMedina
Esri Regular Contributor

Hi John, 

I'm guessing the problem is just that your input JSON is somehow invalid. I've attached another sample input which will add 3 layers to an existing service (point, line, polygon) - it's too long to include as a dictionary here.

from arcgis import GIS
from arcgis.features import FeatureLayerCollection

conn = GIS("https://www.arcgis.com", 
           "Hari", "Seldon")

layer_name = "Foundation"
search_results = conn.content.search(layer_name, item_type="Feature Layer")
proper_index = [i for i, s in enumerate(search_results) 
                if '"' + layer_name + '"' in str(s)]
found_item = search_results[proper_index[0]]
source_flc = FeatureLayerCollection.fromitem(found_item)

add_data = <<< attached data >>>

source_flc.manager.add_to_definition(add)‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Note: You could load the attached data using the json library, but you'd need to change True to true, False to false, None to null....

Hope this helps.

To answer your greater question, is your approach the best approach? That depends - you could certainly just overwrite the existing service with your additional layer and be done with the matter without ever having to guess what the JSON should be. It's really up to you how you want to approach the problem.

-Earl

JohnEvans6
New Contributor III

I may end up doing just that. It seemed like a lot of overhead considering the layers update/add/change frequently. If I end up burning too much time on the above add to definition table, I'll just overwrite the whole service.

Thanks!

0 Kudos