4

My script creates a folder named 'output' and saves processed files there. I am trying to delete this folder and all files within it, if I need to re-run the script (see reproducible example at the bottom).

However, in my first attempt re-running the script I get the following error:

Traceback (most recent call last): File "C:\OSGeo4W\apps\Python39\lib\code.py", line 90, in runcode exec(code, self.locals) File "", line 1, in File "", line 17, in File "C:\OSGeo4W\apps\Python39\lib\shutil.py", line 740, in rmtree return _rmtree_unsafe(path, onerror) File "C:\OSGeo4W\apps\Python39\lib\shutil.py", line 618, in _rmtree_unsafe onerror(os.unlink, fullname, sys.exc_info()) File "C:\OSGeo4W\apps\Python39\lib\shutil.py", line 616, in _rmtree_unsafe os.unlink(fullname) PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'C:/.../output\point_layer.gpkg'

If I try to run it again, then it works. If I try one more time, it does not run (same error); and if I try again, then it works, etc..

When I manually remove the layers, then the script works in the next run. If I manually type QgsProject.instance().removeAllMapLayers() in the Python console after successfully running the script, it will also work in the next run.

Based on what I have researched so far, I guess this happens because the layers in the 'output' folder are not entirely closed/removed from QGIS. How can I make it work?


Reproducible code:

from qgis.core import *
from qgis import processing
import os
import shutil

#Current .qgz folder
dir = QgsProject.instance().readPath("./") + '/'

#Path to output folder
path_output = os.path.join(dir,'output')

#Remove 'output' folder and files within it, if exists
if os.path.exists(path_output):
    #Remove all layers from project
    QgsProject.instance().removeAllMapLayers()
    #Remove output folder and files within it
    shutil.rmtree(f'{path_output}')

#Create 'output' folder, if it does not exist
if not os.path.exists(path_output):
    os.makedirs(path_output)

#Create sample layer
vl = QgsVectorLayer("Point", "temporary_points", "memory")
pr = vl.dataProvider()
vl.startEditing()
pr.addAttributes([QgsField("name", QVariant.String)])
fet = QgsFeature()
fet.setGeometry( QgsGeometry.fromPointXY(QgsPointXY(15,60)) )
fet.setAttributes(["Johny"])
pr.addFeatures([fet])
vl.commitChanges()
QgsProject.instance().addMapLayer(vl)

#Reproject and save new layer in 'output' folder
params = {'INPUT':vl,\
          'TARGET_CRS':QgsCoordinateReferenceSystem('EPSG:4326'),\
          'OUTPUT': f'{path_output}/point_layer.gpkg'}
result = processing.run('qgis:reprojectlayer', params)

#Remove sample layer
QgsProject.instance().removeMapLayer(vl)
#Add reprojected layer
reprojected_layer = QgsVectorLayer(result["OUTPUT"], "new_layer", "ogr")
QgsProject.instance().addMapLayer(reprojected_layer) #adiciona layer pts reprojetado.

1 Answer 1

5

It seems to me a problem of timing, whereby the line: shutil.rmtree(f'{path_output}') is being called before the layer has been completely released. I tried a few approaches to solve the issue including connecting a function to remove the directory to the QgsProject.mapLayersRemoved signal, which I found was unsuccessful.

The only way I was able to reliably make this work was to wrap the entire logic in a function, remove all map layers from the project, then call the function with a small delay using QTimer.singleShot().

from qgis.core import *
from qgis import processing
from PyQt5.QtCore import QTimer
import os
import shutil


def add_layer():
    #Current .qgz folder
    dir = QgsProject.instance().readPath("./") + '/'

    #Path to output folder
    path_output = os.path.join(dir,'output')

    #Remove 'output' folder and files within it, if exists
    if os.path.exists(path_output):
        #Remove output folder and files within it
        shutil.rmtree(path_output)

    #Create 'output' folder, if it does not exist
    if not os.path.exists(path_output):
        os.makedirs(path_output)

    #Create sample layer
    vl = QgsVectorLayer("Point", "temporary_points", "memory")
    pr = vl.dataProvider()
    pr.addAttributes([QgsField("name", QVariant.String)])
    fet = QgsFeature()
    fet.setGeometry( QgsGeometry.fromPointXY(QgsPointXY(15,60)) )
    fet.setAttributes(["Johny"])
    pr.addFeatures([fet])
    QgsProject.instance().addMapLayer(vl)
    
    #Reproject and save new layer in 'output' folder
    params = {'INPUT':vl,\
              'TARGET_CRS':QgsCoordinateReferenceSystem('EPSG:4326'),\
              'OUTPUT': f'{path_output}/point_layer.gpkg'}
    result = processing.run('qgis:reprojectlayer', params)

    #Remove sample layer
    QgsProject.instance().removeMapLayer(vl)
    # Refresh map canvas
    iface.mapCanvas().refresh()
    #Add reprojected layer
    reprojected_layer = QgsVectorLayer(result["OUTPUT"], "new_layer", "ogr")
    QgsProject.instance().addMapLayer(reprojected_layer) #adiciona layer pts reprojetado.
        

# Remove all layers from project
QgsProject.instance().removeAllMapLayers()
# Refresh map canvas
iface.mapCanvas().refresh()
# Call add_layer function with 200ms delay
QTimer.singleShot(200, add_layer)

Interestingly, for me the above code also works with those lines:

if os.path.exists(path_output):
    shutil.rmtree(path_output)

commented out entirely.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.