[processing][needs-docs] Add friendlier API for running algorithms as sub-steps
of main algorithm
Using code like:
buffered_layer = processing.run(..., context, feedback)['OUTPUT']
...
return {'OUTPUT': buffered_layer}
can cause issues if done as a sub-step of a larger processing algorithm. This
is because ownership of the generated layer is transferred to the caller
(Python) by processing.run. When the algorithm returns, Processing
attempts to move ownership of the layer from the context to the caller,
resulting in a crash.
(This is by design, because processing.run has been optimised for the
most common use case, which is one-off execution of algorithms as part
of a script, not as part of another processing algorithm. Accordingly
by design it returns layers and ownership to the caller, making things
easier for callers as they do not then have to resolve the layer reference
from the context object and handle ownership themselves)
This commit adds a new "is_child_algorithm" argument to processing.run.
For algorithms which are executed as sub-steps of a larger algorithm
is_child_algorithm should be set to True to avoid any ownership issues
with layers. E.g.
buffered_layer = processing.run(..., context, feedback, is_child_algorithm=True)['OUTPUT']
...
return {'OUTPUT': buffered_layer}
2019-01-29 12:23:31 +10:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
"""
|
|
|
|
***************************************************************************
|
|
|
|
QgisAlgorithmTests.py
|
|
|
|
---------------------
|
|
|
|
Date : January 2019
|
|
|
|
Copyright : (C) 2019 by Nyall Dawson
|
|
|
|
Email : nyall dot dawson at gmail dot com
|
|
|
|
***************************************************************************
|
|
|
|
* *
|
|
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
|
|
* it under the terms of the GNU General Public License as published by *
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
|
|
* (at your option) any later version. *
|
|
|
|
* *
|
|
|
|
***************************************************************************
|
|
|
|
"""
|
|
|
|
|
|
|
|
__author__ = 'Nyall Dawson'
|
|
|
|
__date__ = 'January 2019'
|
|
|
|
__copyright__ = '(C) 2019, Nyall Dawson'
|
|
|
|
|
|
|
|
import nose2
|
|
|
|
import shutil
|
|
|
|
import gc
|
|
|
|
|
|
|
|
from qgis.core import (QgsApplication,
|
|
|
|
QgsProcessing,
|
|
|
|
QgsProcessingContext,
|
2019-03-14 08:35:32 +10:00
|
|
|
QgsVectorLayer,
|
|
|
|
QgsProject)
|
[processing][needs-docs] Add friendlier API for running algorithms as sub-steps
of main algorithm
Using code like:
buffered_layer = processing.run(..., context, feedback)['OUTPUT']
...
return {'OUTPUT': buffered_layer}
can cause issues if done as a sub-step of a larger processing algorithm. This
is because ownership of the generated layer is transferred to the caller
(Python) by processing.run. When the algorithm returns, Processing
attempts to move ownership of the layer from the context to the caller,
resulting in a crash.
(This is by design, because processing.run has been optimised for the
most common use case, which is one-off execution of algorithms as part
of a script, not as part of another processing algorithm. Accordingly
by design it returns layers and ownership to the caller, making things
easier for callers as they do not then have to resolve the layer reference
from the context object and handle ownership themselves)
This commit adds a new "is_child_algorithm" argument to processing.run.
For algorithms which are executed as sub-steps of a larger algorithm
is_child_algorithm should be set to True to avoid any ownership issues
with layers. E.g.
buffered_layer = processing.run(..., context, feedback, is_child_algorithm=True)['OUTPUT']
...
return {'OUTPUT': buffered_layer}
2019-01-29 12:23:31 +10:00
|
|
|
from qgis.PyQt import sip
|
|
|
|
from qgis.analysis import (QgsNativeAlgorithms)
|
|
|
|
from qgis.testing import start_app, unittest
|
|
|
|
import processing
|
|
|
|
from processing.tests.TestData import points
|
|
|
|
|
|
|
|
|
|
|
|
class TestProcessingGeneral(unittest.TestCase):
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def setUpClass(cls):
|
|
|
|
start_app()
|
|
|
|
from processing.core.Processing import Processing
|
|
|
|
Processing.initialize()
|
|
|
|
QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())
|
|
|
|
cls.cleanup_paths = []
|
|
|
|
cls.in_place_layers = {}
|
|
|
|
cls.vector_layer_params = {}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def tearDownClass(cls):
|
|
|
|
from processing.core.Processing import Processing
|
|
|
|
Processing.deinitialize()
|
|
|
|
for path in cls.cleanup_paths:
|
|
|
|
shutil.rmtree(path)
|
|
|
|
|
|
|
|
def testRun(self):
|
|
|
|
context = QgsProcessingContext()
|
|
|
|
|
|
|
|
# try running an alg using processing.run - ownership of result layer should be transferred back to the caller
|
|
|
|
res = processing.run('qgis:buffer',
|
|
|
|
{'DISTANCE': 1, 'INPUT': points(), 'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT},
|
|
|
|
context=context)
|
|
|
|
self.assertIn('OUTPUT', res)
|
|
|
|
# output should be the layer instance itself
|
|
|
|
self.assertIsInstance(res['OUTPUT'], QgsVectorLayer)
|
|
|
|
# Python should have ownership
|
|
|
|
self.assertTrue(sip.ispyowned(res['OUTPUT']))
|
|
|
|
del context
|
|
|
|
gc.collect()
|
|
|
|
self.assertFalse(sip.isdeleted(res['OUTPUT']))
|
|
|
|
|
|
|
|
# now try using processing.run with is_child_algorithm = True. Ownership should remain with the context
|
|
|
|
context = QgsProcessingContext()
|
|
|
|
res = processing.run('qgis:buffer',
|
|
|
|
{'DISTANCE': 1, 'INPUT': points(), 'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT},
|
|
|
|
context=context, is_child_algorithm=True)
|
|
|
|
self.assertIn('OUTPUT', res)
|
|
|
|
# output should be a layer string reference, NOT the layer itself
|
|
|
|
self.assertIsInstance(res['OUTPUT'], str)
|
|
|
|
layer = context.temporaryLayerStore().mapLayer(res['OUTPUT'])
|
|
|
|
self.assertIsInstance(layer, QgsVectorLayer)
|
|
|
|
# context should have ownership
|
|
|
|
self.assertFalse(sip.ispyowned(layer))
|
|
|
|
del context
|
|
|
|
gc.collect()
|
|
|
|
self.assertTrue(sip.isdeleted(layer))
|
|
|
|
|
2019-03-14 08:35:32 +10:00
|
|
|
def testRunAndLoadResults(self):
|
|
|
|
QgsProject.instance().removeAllMapLayers()
|
|
|
|
context = QgsProcessingContext()
|
|
|
|
|
|
|
|
# try running an alg using processing.runAndLoadResults - ownership of result layer should be transferred to
|
|
|
|
# project, and layer should be present in project
|
|
|
|
res = processing.runAndLoadResults('qgis:buffer',
|
|
|
|
{'DISTANCE': 1, 'INPUT': points(), 'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT},
|
|
|
|
context=context)
|
|
|
|
self.assertIn('OUTPUT', res)
|
|
|
|
# output should be the layer path
|
|
|
|
self.assertIsInstance(res['OUTPUT'], str)
|
|
|
|
|
|
|
|
self.assertEqual(context.layersToLoadOnCompletion()[res['OUTPUT']].project, QgsProject.instance())
|
|
|
|
layer = QgsProject.instance().mapLayer(res['OUTPUT'])
|
|
|
|
self.assertIsInstance(layer, QgsVectorLayer)
|
|
|
|
|
|
|
|
# Python should NOT have ownership
|
|
|
|
self.assertFalse(sip.ispyowned(layer))
|
|
|
|
|
[processing][needs-docs] Add friendlier API for running algorithms as sub-steps
of main algorithm
Using code like:
buffered_layer = processing.run(..., context, feedback)['OUTPUT']
...
return {'OUTPUT': buffered_layer}
can cause issues if done as a sub-step of a larger processing algorithm. This
is because ownership of the generated layer is transferred to the caller
(Python) by processing.run. When the algorithm returns, Processing
attempts to move ownership of the layer from the context to the caller,
resulting in a crash.
(This is by design, because processing.run has been optimised for the
most common use case, which is one-off execution of algorithms as part
of a script, not as part of another processing algorithm. Accordingly
by design it returns layers and ownership to the caller, making things
easier for callers as they do not then have to resolve the layer reference
from the context object and handle ownership themselves)
This commit adds a new "is_child_algorithm" argument to processing.run.
For algorithms which are executed as sub-steps of a larger algorithm
is_child_algorithm should be set to True to avoid any ownership issues
with layers. E.g.
buffered_layer = processing.run(..., context, feedback, is_child_algorithm=True)['OUTPUT']
...
return {'OUTPUT': buffered_layer}
2019-01-29 12:23:31 +10:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
nose2.main()
|