Esri / arcgis-python-api

Documentation and samples for ArcGIS API for Python
https://developers.arcgis.com/python/
Apache License 2.0
1.89k stars 1.1k forks source link

`Map` object fails to initialize when called from an existing `Item` #2109

Open ccone-pattern opened 1 week ago

ccone-pattern commented 1 week ago

Describe the bug Map object fails to populate given an Item of type: WebMap that was previously generated and saved from python. This map item is fully interactable within AGOL.

To Reproduce Steps to reproduce the behavior:

class MapHandler(Handler):

    """
    A `Handler` object that creates and updates map items in AGOL.

    :Attributes:
    :gis: an authenticated `GIS` object.
    :map_id: a string representing the item-id of the web map to be handled.
    :map_properties: a dictionary containing map properties passed to [`arcgis.map.Map().save`](https://developers.arcgis.com/python/latest/api-reference/arcgis.map.toc.html#arcgis.map.Map.save)  
    :wdir: a string defining the folder to create or move the map within AGOL profile.
    """

    gis: GIS = None
    ":attribute gis: an authenticated `GIS` object."
    map_id: str = None
    ":attribute map_id: a string representing the item-id of the web map to be handled."
    map_properties: dict = None
    ":attribute map_properties: a dictionary containing map properties passed to [arcgis.map.Map().save](https://developers.arcgis.com/python/latest/api-reference/arcgis.map.toc.html#arcgis.map.Map.save)  "
    wdir: str = None
    ":attribute wdir: a string defining the folder to create or move the map within AGOL profile."
    basemap: str = "topo-vector"

@classmethod
    def update(cls, layer_items: List[Item]) -> Map:
        """
        Updates the web map by removing all existing layers and adding those provided as the argument `layers`.

        :param layers: List of `Item`, `FeatureLayer`, or `FeatureLayerCollection` objects to add to an existing map.
        :returns: Resulting `Map` created with the given `layers`.
        :rtype: :class:`arcgis.map.Map <https://developers.arcgis.com/python/latest/api-reference/arcgis.map.toc.html#arcgis.map.Map>`

        .. note::

            Given the complexity of the `arcgis` Python API, we chose to remove all layers instead of checking if each layer is contained within the provided list of layers to update the map.

            Testing with the following logic revealed that an `Item` added to a map will be transformed to its child entity (e.g., `FeatureLayer` or `GroupLayer`).

            .. code-block:: python

                if lyr not in map.content.layers:
                   map.content.add(lyr)

            Inconsistencies in the APIs across these implementations made it cumbersome to develop, and we saw no limitation in simply removing everything from the map and then re-adding.
            Users should provide *all* layers they want in the resulting map, not just those they want to add.
        """
        item = cls.gis.content.get(cls.map_id)
        if cls.wdir:
            folder_content = cls.gis.content.folders.get(cls.wdir).list() # Item object does not appear to contain a `folder` attribute as of arcgis 2.4.0 
            if item not in folder_content:
                item.move(cls.wdir)

        wm = Map(item)
        wm.basemap.basemap = cls.basemap
        wm.content.remove_all()
        for item in layer_items:
            # lyr = FeatureCollection.fromitem(item)
            wm.content.add(item)

        success = wm.update()
        if success:
            print(f"Successfully updated map: {cls.map_properties} with {len(wm.content.layers)} layers.")
        else:
            raise RuntimeError(f"Failed to update map item {cls.map_id}")
        return wm

error:

                ^^^^^^
  File "C:\Users\Charlie.Cone\repos\Field Maps Deployment Tool\deploy_field_maps\main.py", line 15, in main
    wm = MapHandler.upsert(layers)
         ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Charlie.Cone\repos\Field Maps Deployment Tool\deploy_field_maps\handlers.py", line 217, in upsert
    return cls.update(layers)
           ^^^^^^^^^^^^^^^^^^
  File "C:\Users\Charlie.Cone\repos\Field Maps Deployment Tool\deploy_field_maps\handlers.py", line 186, in update
    wm = Map(item)
         ^^^^^^^^^
  File "C:\Users\Charlie.Cone\AppData\Local\anaconda3\envs\esri-source\Lib\site-packages\arcgis\map\map_widget.py", line 254, in __init__
    self._setup_webmap_properties(item)
  File "C:\Users\Charlie.Cone\AppData\Local\anaconda3\envs\esri-source\Lib\site-packages\arcgis\map\map_widget.py", line 447, in _setup_webmap_properties
    self._update_source()
  File "C:\Users\Charlie.Cone\AppData\Local\anaconda3\envs\esri-source\Lib\site-packages\arcgis\map\map_widget.py", line 488, in _update_source
    self._webmap_dict = self._webmap.dict()
                        ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Charlie.Cone\AppData\Local\anaconda3\envs\esri-source\Lib\site-packages\arcgis\map\_dataclasses\_webmap_spec.py", line 73, in dict
    d = super().model_dump(
        ^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Charlie.Cone\AppData\Local\anaconda3\envs\esri-source\Lib\site-packages\pydantic\main.py", line 364, in model_dump
    return self.__pydantic_serializer__.to_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: 'MockValSer' object cannot be converted to 'SchemaSerializer'

Screenshots image screenshot from vscode debugger displaying valid item fetched directly before initializing Map

Expected behavior A valid Map object based on the item fetched from GIS.content and/or Explicit description of any invalid json syntax in WebMap's definition

Platform (please complete the following information):

nanaeaubry commented 1 week ago

@ccone-pattern When you say the map was previously made with python do you mean this new version of mapping or with the older WebMap class?

Can you post a screenshot of the actual issue or provide an example item for this? The one you have posted suggests that something is being passed into the dataclasses that it does not recognize.

Also this may not be it but can you initialize the map with Map(item=item) ? The first parameter (by user demand) is location and not item. We have a check in the code but it's not always foolproof.