Select to view content in your preferred language

Add support for pathlib.Path objects to arcpy

11913
29
04-09-2020 09:20 AM
Status: Implemented
ScottDavis
Frequent Contributor

pathlib seems to be the preferred method for working with system paths in Python 3. We've started using it in place of `os.path` in our office recently and really like it. However, we've run into problems with using it with arcpy. For example:

workspace = pathlib.Path('C:\some path') \ 'anotherfolder' \ 'connection.sde'
with arcpy.EnvManager(workspace=workspace):
    pass‍‍‍‍‍‍

Throws this error: "RuntimeError: Object: Error in accessing environment <workspace>"

To work around this, we end up wrapping all of our Path object with str(). For example:

workspace = str(pathlib.Path('C:\some path') \ 'anotherfolder' \ 'connection.sde')
with arcpy.EnvManager(workspace=workspace):
    pass‍‍‍‍‍‍‍‍‍

It would be great if arcpy (Pro version) handled these Path objects natively!

Tags (2)
29 Comments
philnagel

Great news, thank you for sharing that update!

Ethan_Cranmer

Per the arcpy team at the Dev Summit this is releasing with 3.7. All tools will work with path objects as inputs. An env setting needs to be set for outputs.

HannesZiegler
Status changed to: Implemented

Congratulations!  The idea made its way into the software - geoprocessing tools and ArcPy functionality support Python pathlib.Path objects, allowing paths to be passed directly without being converted to strings.

Learn more about using pathlib.Path with geoprocessing tools

BlakeTerhune

Lovely! Thank you for the great communication, @HannesZiegler

HaydenWelch

Currently, pathlib.Path is unsupported by arcpy objects and functions that take filepath args.

Since pathlib.Path is common and can almost always be safely converted to a string path with str(path) or str(path.resolve()) or path.resolve().__fspath__(), I think it should be okay to cast it to a string in the conversion function:

 

def gp_fixarg(arg, string_results, pass_arc_object):
    from arcpy.arcobjects import Result
    from pathlib import Path
    if isinstance(arg, Result) and string_results:
        return str(arg._arc_object)
    if isinstance(arg, (tuple, list)):
        return gp_fixargs(arg)
    if hasattr(arg, '_arc_object') and pass_arc_object:
        return arg._arc_object
    if isinstance(arg, Path):
        return str(arg.resolve())
    return arg

 

This can also be monkey patched if you don't want to edit the _base module:

import arcpy

def allow_pathlib_path(func):
    from pathlib import Path
    def _wrapped(arg, *flags):
        if isinstance(arg, Path):
            return str(arg.resolve())
        return func(arg, *flags)
    return _wrapped

arcpy.geoprocessing._base.gp_fixarg = allow_pathlib_path(arcpy.geoprocessing._base.gp_fixarg)

 

Also since path resolution can be slow, you could wrap the inner function in an lru_cache decorator (so if a Path object is passed in a tight loop, it won't be resolved each time)

import arcpy
from pathlib import Path
from functools import lru_cache

def allow_pathlib_path(func):
    @lru_cache
    def _wrapped(arg, *flags):
        if isinstance(arg, Path):
            return str(arg.resolve())
        return func(arg, *flags)
    return _wrapped

arcpy.geoprocessing._base.gp_fixarg = allow_pathlib_path(arcpy.geoprocessing._base.gp_fixarg)
AlfredBaldenweck

This may be supported now in 3.7?

ArcPy

What's new in ArcGIS Pro 3.7 | ArcGIS Pro documentation

HaydenWelch

@AlfredBaldenweck Awesome I'm still on 3.5 so I've been using the monkey patch since everything I write uses pathlib lol. I wonder if they just did exactly this...

DanPatterson

It is definitely there, line 2739 and beyond

C:\...your_install_folder...\Resources\ArcPy\arcpy\__init__.py

# =============================================================================
# Pathlib support: Enable pathlib.Path objects to work with all ArcPy functions
# =============================================================================

HaydenWelch

@DanPatterson I really need to update, the str(Path(...) / ... / ...) workaround was starting to get painful