Skip to content

Commit 3a33209

Browse files
JoonalaiLKajan
authored andcommitted
Add the clean_qgis_layer decorator back
1 parent d782092 commit 3a33209

File tree

4 files changed

+82
-2
lines changed

4 files changed

+82
-2
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Unreleased
22

3+
## New Features
4+
5+
* Add `clean_qgis_layer` decorator back alongside with automatic cleaning [#45](https://github.com/GispoCoding/pytest-qgis/pull/45)
6+
7+
38
## Fixes
49

510
* [#55](https://github.com/GispoCoding/pytest-qgis/pull/55) Allows using MagicMocks to mock layers without problems

README.md

+29
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,35 @@ markers can be used.
5858
5959
* `pytest_runtest_teardown` hook is used to ensure that all layer fixtures of any scope are cleaned properly without causing segmentation faults. The layer fixtures that are cleaned automatically must have some of the following keywords in their name: "layer", "lyr", "raster", "rast", "tif".
6060

61+
62+
### Utility tools
63+
64+
* `clean_qgis_layer` decorator found in `pytest_qgis.utils` can be used with `QgsMapLayer` fixtures to ensure that they
65+
are cleaned properly if they are used but not added to the `QgsProject`. This is only needed with layers with other than memory provider.
66+
67+
This decorator works only with fixtures that **return** QgsMapLayer instances.
68+
There is no support for fixtures that use yield.
69+
70+
This decorator is an alternative way of cleaning the layers, since `pytest_runtest_teardown` hook cleans layer fixtures automatically by the keyword.
71+
72+
```python
73+
# conftest.py or start of a test file
74+
import pytest
75+
from pytest_qgis.utils import clean_qgis_layer
76+
from qgis.core import QgsVectorLayer
77+
78+
@pytest.fixture()
79+
@clean_qgis_layer
80+
def geojson() -> QgsVectorLayer:
81+
return QgsVectorLayer("layer_file.geojson", "some layer")
82+
83+
# This will be cleaned automatically since it contains the keyword "layer" in its name
84+
@pytest.fixture()
85+
def geojson_layer() -> QgsVectorLayer:
86+
return QgsVectorLayer("layer_file2.geojson", "some layer")
87+
```
88+
89+
6190
### Command line options
6291

6392
* `--qgis_disable_gui` can be used to disable graphical user interface in tests. This speeds up the tests that use Qt

src/pytest_qgis/utils.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
#
1919
import time
2020
from collections import Counter
21+
from functools import wraps
2122
from pathlib import Path
22-
from typing import TYPE_CHECKING, Any, Optional
23+
from typing import TYPE_CHECKING, Any, Callable, Generator, Optional
2324
from unittest.mock import MagicMock
2425

2526
from osgeo import gdal
@@ -182,6 +183,36 @@ def copy_layer_style_and_position(
182183
group.insertLayer(index + 1, layer2)
183184

184185

186+
def clean_qgis_layer(fn: Callable[..., QgsMapLayer]) -> Callable[..., QgsMapLayer]:
187+
"""
188+
Decorator to ensure that a map layer created by a fixture is cleaned properly.
189+
190+
Sometimes fixture non-memory layers that are used but not added
191+
to the project might cause segmentation fault errors.
192+
193+
This decorator works only with fixtures that **return** QgsMapLayer instances.
194+
There is no support for fixtures that use yield.
195+
196+
>>> @pytest.fixture()
197+
>>> @clean_qgis_layer
198+
>>> def geojson_layer() -> QgsVectorLayer:
199+
>>> layer = QgsVectorLayer("layer.json", "layer", "ogr")
200+
>>> return layer
201+
202+
This decorator is the alternative way of cleaning the layers since layer fixtures
203+
are automatically cleaned if they contain one of the keywords listed in
204+
LAYER_KEYWORDS by pytest_runtest_teardown hook.
205+
"""
206+
207+
@wraps(fn)
208+
def wrapper(*args: Any, **kwargs: Any) -> Generator[QgsMapLayer, None, None]:
209+
layer = fn(*args, **kwargs)
210+
yield layer
211+
_set_layer_owner_to_project(layer)
212+
213+
return wrapper
214+
215+
185216
def ensure_qgis_layer_fixtures_are_cleaned(request: "FixtureRequest") -> None:
186217
"""
187218
Sometimes fixture non-memory layers that are used but not added

tests/test_utils.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818

1919
import pytest
2020
from pytest_qgis.utils import (
21+
clean_qgis_layer,
2122
get_common_extent_from_all_layers,
2223
get_layers_with_different_crs,
2324
replace_layers_with_reprojected_clones,
2425
set_map_crs_based_on_layers,
2526
)
26-
from qgis.core import QgsCoordinateReferenceSystem, QgsProject
27+
from qgis.core import QgsCoordinateReferenceSystem, QgsProject, QgsVectorLayer
28+
from qgis.PyQt import sip
2729

2830
from tests.utils import EPSG_3067, EPSG_4326, QGIS_VERSION
2931

@@ -98,3 +100,16 @@ def test_replace_layers_with_reprojected_clones( # noqa: PLR0913
98100
assert layers[raster_layer_name].crs().authid() == EPSG_4326
99101
assert (tmp_path / f"{vector_layer_id}.qml").exists()
100102
assert (tmp_path / f"{raster_layer_id}.qml").exists()
103+
104+
105+
def test_clean_qgis_layer(layer_polygon):
106+
layer = QgsVectorLayer(layer_polygon.source(), "another layer")
107+
108+
@clean_qgis_layer
109+
def layer_function() -> QgsVectorLayer:
110+
return layer
111+
112+
# Using list to trigger yield and the code that runs after it
113+
list(layer_function())
114+
115+
assert sip.isdeleted(layer)

0 commit comments

Comments
 (0)