As @jdavid05 said, the secondary loop within the file loop is causing the error. the root
returned by a os.walk
is a string:
[ ('<root>', ['<dir>', ...], ['<file>', ...]), ... ]
with fake paths:
# root dirs files
('C:\User', ['MyPhotos', 'MyDocuments'], ['invoice.txt', 'meme.jpg']),
('C:\User\MyPhotos', ['Vacation'], ['profile.jpg']),
('C:\User\MyPhotos\Vacation', [], ['arrival.jpg', 'departure.jpg']),
('C:\User\MyDocuments', ['Memoir'], ['bill.pdf']),
('C:\User\MyDocuments\Memoir', [], []),
With each subsequent tuple in that os.walk
iterator being the original root joined to the next dir in the dir list so:
for root, dirs, files in os.walk(r'<path>'):
for file in files:
...
for path in root:
print(path)
Would result in:
>>> 'C'
>>> ':'
>>> '\'
...
for every aprx file below the top of the walk.
The only thing you need to do with os.walk
to get every file in every subdirectory of a root directory is:
for root, _, files in os.walk(r'<path>'):
for file in files:
if not file.endswith('.<extension>'): continue
file_path = os.path.join(root, file)
# do something with file path
The third line there allows you to save an indent level by advancing the iterator on a pattern mismatch instead of shielding your code with a pattern match
So knowing how walk works, now we can implement some clean code that uses it properly:
import arcpy, os
def batch_update_connections(workspace: os.PathLike, old_path: os.PathLike, new_path: os.PathLike) -> None:
for root, _, files in os.walk(workspace):
for file in files:
# Skip if not an ArcGIS project file
if not file.endswith('.aprx'): continue
# move into the root folder
os.chdir(root)
old_path = os.path.abspath(old_path)
new_path = os.path.abspath(new_path)
# Create output folder <root>/<file>_updated
out_folder = os.path.join(root, f"{file.split('.')[0]}_updated")
out_file = os.path.join(out_folder, file)
os.makedirs(out_folder, exist_ok=True)
# Skip and warn if output file already exists
if os.path.exists(out_file): print(f"{out_file} in {out_folder} already updated") ;continue
# Update connections and save to output folder
aprx = arcpy.mp.ArcGISProject(os.path.join(root, file))
aprx.updateConnectionProperties(old_path, new_path)
aprx.saveACopy(out_file)
# Notify user of completion
print(f'Updated connections in {file}, saved to {out_folder}.')
def main():
workspace = r'\path\to\workspace'
#workspace = os.getcwd() # Use current working directory
old_path = r'\path\to\old\connection'
new_path = r'\path\to\new\connection'
batch_update_connections(workspace, old_path, new_path)
if __name__ == '__main__':
main()
- Define a batch update function that takes a workspace and the connection paths
- Walk the workspace ignoring dirs (root,
_
, files)
- Immediately skip any file that does not end with
.aprx
- Move into the current root to allow for relative paths to be used (
../data
)
- Convert the paths to absolute paths if relative paths were used
- Create the output folder path and the output file path
- Use
os.makedirs
to avoid having to write a try: except
block
- Skip and warn the user if the project has already been updated
- Open the project, update the connection, and save it to the output folder
- Wrap all the functionality in a
main()
function and call it in if __name__ == '__main__'
block
The last step makes this a standalone script that can be run directly from the terminal. If a lot of people need to use this and all you want them to do is run it, you can toggle the os.getcwd()
line and have them run it from the root of their project directory. All you need to do is set up the relative or absolute paths for the old and new datasources.