Repository: geofileops/geofileops
Pull request #672 (2025-03-24 14:05:40)

BUG: fix listlayers returns layer name for a non-existing .shp
`listlayers` should raise a FileNotFoundError when the .shp path does not exist instead of returning the stem.

This also lead to an issue when `copy_layer` was used with `write_mode="add_layer"` and an .shp as destination since #669.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/geofileops/geofileops/pull/672?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=geofileops) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 85.90%. Comparing base [(`5f08842`)](https://app.codecov.io/gh/geofileops/geofileops/commit/5f08842e475a5aaff2f4ace75e7bc77c9c2fe023?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=geofileops) to head [(`157e2aa`)](https://app.codecov.io/gh/geofileops/geofileops/commit/157e2aafa8974e240ebccac995ed0e2dbf7407c6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=geofileops).
> Report is 135 commits behind head on main.

> :exclamation:  There is a different number of reports uploaded between BASE (5f08842) and HEAD (157e2aa). Click for more details.
> 
> <details><summary>HEAD has 5 uploads less than BASE</summary>
>
>| Flag | BASE (5f08842) | HEAD (157e2aa) |
>|------|------|------|
>||6|1|
></details>

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #672      +/-   ##
==========================================
- Coverage   93.11%   85.90%   -7.22%     
==========================================
  Files          21       22       +1     
  Lines        4113     4590     +477     
==========================================
+ Hits         3830     3943     +113     
- Misses        283      647     +364     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/geofileops/geofileops/pull/672?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=geofileops).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=geofileops).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e16f4fb..0b4828c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -58,6 +58,7 @@
 - Fix an invalid output .gpkg file being created when e.g. `copy_layer` is ran with an
   invalid sql statement (#641)
 - Fix wrong results for `export_by_location` with queries != "intersects is True" (#617)
+- Fix listlayers returns layer name for a non-existing .shp (#672)
 
 ## 0.9.1 (2024-07-18)
 
diff --git a/geofileops/fileops.py b/geofileops/fileops.py
index 24a572b..214e382 100644
--- a/geofileops/fileops.py
+++ b/geofileops/fileops.py
@@ -113,6 +113,8 @@ def listlayers(
     """  # noqa: E501
     path_p = Path(path)
     if path_p.suffix.lower() == ".shp":
+        if not _vsi_exists(path):
+            raise FileNotFoundError(f"File not found: {path}")
         return [path_p.stem]
 
     datasource = None


Test patch:
diff --git a/tests/test_geofile.py b/tests/test_geofile.py
index 0c420c5..7b75662 100644
--- a/tests/test_geofile.py
+++ b/tests/test_geofile.py
@@ -245,7 +245,7 @@ def test_copy_layer(tmp_path, testfile, dimensions, suffix_input, suffix_output)
         assert src_layerinfo.geometrytypename == dst_layerinfo.geometrytypename
 
 
-def test_copy_layer_add_layer(tmp_path):
+def test_copy_layer_add_layer_gpkg(tmp_path):
     # Prepare test data
     src = test_helper.get_testfile("polygon-parcel")
     dst = tmp_path / "output.gpkg"
@@ -289,6 +289,20 @@ def test_copy_layer_add_layer(tmp_path):
     assert layer2_info.featurecount == 48
 
 
+def test_copy_layer_add_layer_shp(tmp_path):
+    # Prepare test data
+    src = test_helper.get_testfile("polygon-parcel")
+    dst = tmp_path / "output.shp"
+    layer1 = gfo.get_default_layer(dst)
+
+    # First "add_layer" to file already while the file doesn't exist yet
+    gfo.copy_layer(src, dst, dst_layer=layer1, write_mode="add_layer")
+
+    # Check result
+    layer1_info = gfo.get_layerinfo(dst, layer1)
+    assert layer1_info.featurecount == 48
+
+
 def test_copy_layer_append_different_layer(tmp_path):
     # Prepare test data
     src_path = test_helper.get_testfile("polygon-parcel", dst_dir=tmp_path)
@@ -1164,8 +1178,9 @@ def test_is_geofile_deprecated():
     assert gfo.is_geofile("/test/testje.txt") is False
 
 
-def test_listlayers_errors():
-    path = "not_existing_file.gpkg"
+@pytest.mark.parametrize("suffix", SUFFIXES_FILEOPS)
+def test_listlayers_errors(suffix):
+    path = f"not_existing_file{suffix}"
     with pytest.raises(FileNotFoundError, match=f"File not found: {path}"):
         _ = gfo.listlayers(path)
 
@@ -1939,8 +1954,12 @@ def test_to_file_fid_append_to(tmp_path, engine_setter):
     # Now merge them, but start with the high fid numbers
     gfo.copy(output2_path, output_path)
     gfo.rename_layer(output_path, output_path.stem)
-    gfo.append_to(
-        output1_path, output_path, dst_layer=output_path.stem, preserve_fid=True
+    gfo.copy_layer(
+        output1_path,
+        output_path,
+        dst_layer=output_path.stem,
+        write_mode="append",
+        preserve_fid=True,
     )
 
     # Prepare expected result
diff --git a/tests/test_helper.py b/tests/test_helper.py
index 8058466..9eb93e1 100644
--- a/tests/test_helper.py
+++ b/tests/test_helper.py
@@ -192,9 +192,9 @@ def get_testfile(
                 tmp_path,
                 src_layer=src_layer,
                 dst_layer=dst_layer,
+                write_mode="add_layer",
                 dst_crs=epsg,
                 reproject=True,
-                append=True,
                 preserve_fid=not explodecollections,
                 dst_dimensions=dimensions,
                 explodecollections=explodecollections,