Skip to content

Commit

Permalink
Add the clean_qgis_layer decorator back
Browse files Browse the repository at this point in the history
  • Loading branch information
Joonalai authored and LKajan committed Jun 14, 2024
1 parent d782092 commit 3a33209
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 2 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Unreleased

## New Features

* Add `clean_qgis_layer` decorator back alongside with automatic cleaning [#45](https://github.com/GispoCoding/pytest-qgis/pull/45)


## Fixes

* [#55](https://github.com/GispoCoding/pytest-qgis/pull/55) Allows using MagicMocks to mock layers without problems
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,35 @@ markers can be used.

* `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".


### Utility tools

* `clean_qgis_layer` decorator found in `pytest_qgis.utils` can be used with `QgsMapLayer` fixtures to ensure that they
are cleaned properly if they are used but not added to the `QgsProject`. This is only needed with layers with other than memory provider.

This decorator works only with fixtures that **return** QgsMapLayer instances.
There is no support for fixtures that use yield.

This decorator is an alternative way of cleaning the layers, since `pytest_runtest_teardown` hook cleans layer fixtures automatically by the keyword.

```python
# conftest.py or start of a test file
import pytest
from pytest_qgis.utils import clean_qgis_layer
from qgis.core import QgsVectorLayer

@pytest.fixture()
@clean_qgis_layer
def geojson() -> QgsVectorLayer:
return QgsVectorLayer("layer_file.geojson", "some layer")

# This will be cleaned automatically since it contains the keyword "layer" in its name
@pytest.fixture()
def geojson_layer() -> QgsVectorLayer:
return QgsVectorLayer("layer_file2.geojson", "some layer")
```


### Command line options

* `--qgis_disable_gui` can be used to disable graphical user interface in tests. This speeds up the tests that use Qt
Expand Down
33 changes: 32 additions & 1 deletion src/pytest_qgis/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
#
import time
from collections import Counter
from functools import wraps
from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional
from typing import TYPE_CHECKING, Any, Callable, Generator, Optional
from unittest.mock import MagicMock

from osgeo import gdal
Expand Down Expand Up @@ -182,6 +183,36 @@ def copy_layer_style_and_position(
group.insertLayer(index + 1, layer2)


def clean_qgis_layer(fn: Callable[..., QgsMapLayer]) -> Callable[..., QgsMapLayer]:
"""
Decorator to ensure that a map layer created by a fixture is cleaned properly.
Sometimes fixture non-memory layers that are used but not added
to the project might cause segmentation fault errors.
This decorator works only with fixtures that **return** QgsMapLayer instances.
There is no support for fixtures that use yield.
>>> @pytest.fixture()
>>> @clean_qgis_layer
>>> def geojson_layer() -> QgsVectorLayer:
>>> layer = QgsVectorLayer("layer.json", "layer", "ogr")
>>> return layer
This decorator is the alternative way of cleaning the layers since layer fixtures
are automatically cleaned if they contain one of the keywords listed in
LAYER_KEYWORDS by pytest_runtest_teardown hook.
"""

@wraps(fn)
def wrapper(*args: Any, **kwargs: Any) -> Generator[QgsMapLayer, None, None]:
layer = fn(*args, **kwargs)
yield layer
_set_layer_owner_to_project(layer)

return wrapper


def ensure_qgis_layer_fixtures_are_cleaned(request: "FixtureRequest") -> None:
"""
Sometimes fixture non-memory layers that are used but not added
Expand Down
17 changes: 16 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@

import pytest
from pytest_qgis.utils import (
clean_qgis_layer,
get_common_extent_from_all_layers,
get_layers_with_different_crs,
replace_layers_with_reprojected_clones,
set_map_crs_based_on_layers,
)
from qgis.core import QgsCoordinateReferenceSystem, QgsProject
from qgis.core import QgsCoordinateReferenceSystem, QgsProject, QgsVectorLayer
from qgis.PyQt import sip

from tests.utils import EPSG_3067, EPSG_4326, QGIS_VERSION

Expand Down Expand Up @@ -98,3 +100,16 @@ def test_replace_layers_with_reprojected_clones( # noqa: PLR0913
assert layers[raster_layer_name].crs().authid() == EPSG_4326
assert (tmp_path / f"{vector_layer_id}.qml").exists()
assert (tmp_path / f"{raster_layer_id}.qml").exists()


def test_clean_qgis_layer(layer_polygon):
layer = QgsVectorLayer(layer_polygon.source(), "another layer")

@clean_qgis_layer
def layer_function() -> QgsVectorLayer:
return layer

# Using list to trigger yield and the code that runs after it
list(layer_function())

assert sip.isdeleted(layer)

0 comments on commit 3a33209

Please sign in to comment.