[Git][debian-gis-team/pyshp][master] 4 commits: New upstream version 3.0.13
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Sat Jun 20 04:50:42 BST 2026
Bas Couwenberg pushed to branch master at Debian GIS Project / pyshp
Commits:
2bbf00cf by Bas Couwenberg at 2026-06-20T05:46:40+02:00
New upstream version 3.0.13
- - - - -
64810e19 by Bas Couwenberg at 2026-06-20T05:46:49+02:00
Update upstream source from tag 'upstream/3.0.13'
Update to upstream version '3.0.13'
with Debian dir 97c4abc9d0edb1c61e4c4362635c21475806c21a
- - - - -
0a168322 by Bas Couwenberg at 2026-06-20T05:47:09+02:00
New upstream release.
- - - - -
9b3fa835 by Bas Couwenberg at 2026-06-20T05:47:38+02:00
Set distribution to unstable.
- - - - -
5 changed files:
- README.md
- changelog.txt
- debian/changelog
- src/shapefile.py
- tests/hypothesis_tests.py
Changes:
=====================================
README.md
=====================================
@@ -8,8 +8,8 @@ The Python Shapefile Library (PyShp) reads and writes ESRI Shapefiles in pure Py
- **Author**: [Joel Lawhead](https://github.com/GeospatialPython)
- **Maintainers**: [James Parrott](https://github.com/JamesParrott) & [Karim Bahgat](https://github.com/karimbahgat)
-- **Version**: 3.0.12
-- **Date**: 5th June 2026
+- **Version**: 3.0.13
+- **Date**: 19th June 2026
- **License**: [MIT](https://github.com/GeospatialPython/pyshp/blob/master/LICENSE.TXT)
## Contents
@@ -93,6 +93,12 @@ part of your geospatial project.
# Version Changes
+## 3.0.13
+### Bug fix
+ - Fix bug when reading empty shp files.
+### Testing
+ - Add round trip tests for Multipatch and shp files (both passed).
+
## 3.0.12
### Data consistency
- Add Shape.points_2D and Shape.points_3D properties - lists of guaranteed length tuples (2 and 3 respectively).
=====================================
changelog.txt
=====================================
@@ -1,3 +1,13 @@
+VERSION 3.0.13
+
+2026-06-19
+ Bug fix
+ * Fix bug when reading empty shp files.
+
+ Testing
+ * Add round trip tests for Multipatch and shp files (both passed).
+
+
VERSION 3.0.12
2026-06-05
=====================================
debian/changelog
=====================================
@@ -1,3 +1,10 @@
+pyshp (3.0.13-1) unstable; urgency=medium
+
+ * Team upload.
+ * New upstream release.
+
+ -- Bas Couwenberg <sebastic at debian.org> Sat, 20 Jun 2026 05:47:30 +0200
+
pyshp (3.0.12-1) unstable; urgency=medium
* Team upload.
=====================================
src/shapefile.py
=====================================
@@ -8,7 +8,7 @@ Compatible with Python versions >=3.9
from __future__ import annotations
-__version__ = "3.0.12"
+__version__ = "3.0.13"
import abc
import array
@@ -3141,6 +3141,8 @@ class ShpReader(_HasCheckedReadableFile):
# MAYBE: check if more left of file or exit early?
return
+ n = 0 # counter for num of actual shapes found
+
# No shx file, unknown nr of shapes
# Instead iterate until reach end of file
# Calculate the offset indices during iteration
@@ -3149,10 +3151,10 @@ class ShpReader(_HasCheckedReadableFile):
shape = self._shape(shape_len_B=shape_len_B, oid=i, bbox=bbox)
# # pos = self.file.tell()
# pos += num_bytes
+ n += 1
if shape is not None or outside_bbox_as_None:
yield shape
- n = i + 1 # num shapes yielded, having iterated over entire shp file.
assert n == len(self.headers_cache), f"{n=}, {len(self.headers_cache)=}"
=====================================
tests/hypothesis_tests.py
=====================================
@@ -1,12 +1,13 @@
from __future__ import annotations
import io
+import itertools
import pytest
from hypothesis import HealthCheck, given, settings
from hypothesis.strategies import (
builds,
- composite,
+ composite, # Preferably avoid. Shrinking composite strategies is slow.
floats,
integers,
just,
@@ -14,6 +15,7 @@ from hypothesis.strategies import (
none,
one_of,
tuples,
+ sampled_from,
)
import shapefile as shp
@@ -26,14 +28,14 @@ zs = one_of(just(0.0), float_nums)
PointsLengths = integers(min_value=1, max_value=8000) # length of points
oid = one_of(none(), integers(min_value=0))
point_2D = builds(shp.Point, x=xs, y=ys, oid=oid)
-pointM = builds(
+pointm = builds(
shp.PointM,
x=xs,
y=ys,
m=ms,
oid=oid,
)
-pointZ = builds(
+pointz = builds(
shp.PointZ,
x=xs,
y=ys,
@@ -78,7 +80,7 @@ def test_Point_2D_roundtrips(
@pytest.mark.hypothesis
- at given(expected=pointM, i=integers(min_value=1))
+ at given(expected=pointm, i=integers(min_value=1))
def test_PointM_roundtrips(
expected: shp.Point,
i: int,
@@ -102,7 +104,7 @@ def test_PointM_roundtrips(
@pytest.mark.hypothesis
- at given(expected=pointZ, i=integers(min_value=1))
+ at given(expected=pointz, i=integers(min_value=1))
def test_PointZ_roundtrips(
expected: shp.Point,
i: int,
@@ -152,19 +154,17 @@ def test_MultiPoint_roundtrips(
assert actual.oid == expected.oid
- at composite
-def multipointM(draw):
- N = draw(PointsLengths)
- return shp.MultiPointM(
- points=draw(coords_2D_list(min_size=N, max_size=N)),
- m=draw(lists(ms, min_size=N, max_size=N)),
- oid=oid,
- )
+def multipointM_from_xyms(point_ms: tuple[float, float, float | None], oid_: int) -> shp.MultiPointM:
+ x_vals, y_vals, m_vals = zip(*point_ms)
+ xy_vals = zip(x_vals, y_vals)
+ return shp.MultiPointM(points=list(xy_vals), m=list(m_vals), oid=oid_)
+
+multipointm = builds(multipointM_from_xyms, lists(tuples(xs, ys, ms), min_size=1), oid)
@pytest.mark.hypothesis
- at settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
- at given(expected=multipointM(), i=integers(min_value=1))
+# @settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
+ at given(expected=multipointm, i=integers(min_value=1))
def test_MultiPointM_roundtrips(
expected: shp.MultiPointM,
i: int,
@@ -187,20 +187,17 @@ def test_MultiPointM_roundtrips(
assert actual.oid == expected.oid
- at composite
-def multipointZ(draw):
- N = draw(PointsLengths)
- return shp.MultiPointZ(
- points=draw(coords_2D_list(min_size=N, max_size=N)),
- z=draw(lists(zs, min_size=N, max_size=N)),
- m=draw(lists(ms, min_size=N, max_size=N)),
- oid=oid,
- )
+def multipointZ_from_xyzms(pointz_ms: tuple[float, float, float, float | None], oid_: int) -> shp.MultiPointZ:
+ x_vals, y_vals, z_vals, m_vals = zip(*pointz_ms)
+ xy_vals = zip(x_vals, y_vals)
+ return shp.MultiPointZ(points=list(xy_vals), z=list(z_vals), m=list(m_vals), oid=oid_)
+
+multipointz = builds(multipointZ_from_xyzms, lists(tuples(xs, ys, zs, ms), min_size=1), oid)
@pytest.mark.hypothesis
- at settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
- at given(expected=multipointZ(), i=integers(min_value=1))
+# @settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
+ at given(expected=multipointz, i=integers(min_value=1))
def test_MultiPointZ_roundtrips(
expected: shp.MultiPointZ,
i: int,
@@ -251,7 +248,7 @@ def test_Polyline_roundtrips(
assert actual.oid == expected.oid
@pytest.mark.hypothesis
- at settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
+# @settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
@given(expected=polylinem, i=integers(min_value=1))
def test_PolylineM_roundtrips(
expected: shp.PolylineM,
@@ -276,7 +273,7 @@ def test_PolylineM_roundtrips(
assert actual.oid == expected.oid
@pytest.mark.hypothesis
- at settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
+# @settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
@given(expected=polylinez, i=integers(min_value=1))
def test_PolylineZ_roundtrips(
expected: shp.PolylineZ,
@@ -330,7 +327,7 @@ def test_Polygon_roundtrips(
assert actual.oid == expected.oid
@pytest.mark.hypothesis
- at settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
+# @settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
@given(expected=polygonm, i=integers(min_value=1))
def test_PolygonM_roundtrips(
expected: shp.PolygonM,
@@ -355,7 +352,7 @@ def test_PolygonM_roundtrips(
assert actual.oid == expected.oid
@pytest.mark.hypothesis
- at settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
+# @settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
@given(expected=polygonz, i=integers(min_value=1))
def test_PolygonZ_roundtrips(
expected: shp.PolygonZ,
@@ -378,4 +375,111 @@ def test_PolygonZ_roundtrips(
assert actual.parts == expected.parts, f"{type(actual.parts)=}, {type(expected.parts)=}"
assert actual.m == expected.m, f"{type(actual.m)=}, {type(expected.m)=}"
assert actual.z == expected.z, f"{type(actual.z)=}, {type(expected.z)=}"
- assert actual.oid == expected.oid
\ No newline at end of file
+ assert actual.oid == expected.oid
+
+part_types = sampled_from(range(6)) # 0: Triangle Strip, ..., 5: Ring
+
+def multipatch_from_xyzms_and_types(
+ xyzms_and_types: list[tuple[list[tuple[float, float, float, float | None]], int]],
+ oid: int,
+ ) -> shp.MultiPatch:
+ xyzm_vals, p_types = zip(*xyzms_and_types)
+ return shp.MultiPatch(lines = xyzm_vals, partTypes = p_types, oid=oid)
+
+multipatch = builds(
+ multipatch_from_xyzms_and_types,
+ lists(tuples(lists(tuples(xs, ys, zs, ms), min_size=1), part_types), min_size=1), oid)
+
+
+ at pytest.mark.hypothesis
+# @settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
+ at given(expected=multipatch, i=integers(min_value=1))
+def test_MultiPatch_roundtrips(
+ expected: shp.MultiPatch,
+ i: int,
+) -> None:
+ stream = io.BytesIO()
+ n = shp.MultiPatch.write_to_byte_stream(b_io=stream, s=expected, i=i)
+ assert n == stream.tell()
+ stream.seek(0)
+ actual = shp.MultiPatch.from_byte_stream(
+ shapeType=shp.MULTIPATCH,
+ b_io=stream,
+ next_shape_pos=n,
+ oid=expected.oid,
+ bbox=None,
+ )
+ assert isinstance(actual, shp.MultiPatch)
+ assert actual.points_3D == expected.points_3D
+
+ assert actual.parts == expected.parts, f"{type(actual.parts)=}, {type(expected.parts)=}"
+ assert actual.m == expected.m, f"{type(actual.m)=}, {type(expected.m)=}"
+ assert actual.z == expected.z, f"{type(actual.z)=}, {type(expected.z)=}"
+ assert actual.oid == expected.oid
+ assert actual.partTypes == expected.partTypes, f"{type(actual.partTypes)=}, {type(expected.partTypes)=}"
+
+
+shape_codes_names_and_strategies = [
+# (0, "Null Shape"),
+(1, "Point", point_2D),
+(3, "PolyLine", polyline),
+(5, "Polygon", polygon),
+(8, "MultiPoint", multipoint),
+(11, "PointZ", pointz),
+(13, "PolyLineZ", polylinez),
+(15, "PolygonZ", polygonz),
+(18, "MultiPointZ", multipointz),
+(21, "PointM", pointm),
+(23, "PolyLineM", polylinem),
+(25, "PolygonM", polygonm),
+(28, "MultiPointM", multipointm),
+(31, "MultiPatch", multipatch),
+]
+
+def code_and_shape_strat_from_triple(t):
+ x, _name, shapes = t
+ return tuples(just(x), lists(shapes, min_size = 0)) # Empty shp files are in the esri spec.
+
+codes_and_shapes_strats = [
+ code_and_shape_strat_from_triple(t)
+ for t in shape_codes_names_and_strategies
+]
+
+codes_and_shapes = one_of(codes_and_shapes_strats)
+
+ at pytest.mark.hypothesis
+# @settings(suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large])
+ at given(codes_and_shapes=codes_and_shapes)
+def test_shp_reader_writer_roundtrip(codes_and_shapes)-> None:
+ code_ex, expected_shapes = codes_and_shapes
+ stream = io.BytesIO()
+ with shp.ShpWriter(shp=stream, shapeType=code_ex) as w:
+ for shape in expected_shapes:
+ w.shape(shape)
+ stream.seek(0)
+ with shp.ShpReader(shp=stream) as r:
+ assert r.shapeType == code_ex
+
+ for actual, expected in itertools.zip_longest(r.shapes(), expected_shapes):
+
+ assert isinstance(actual, shp.SHAPE_CLASS_FROM_SHAPETYPE[code_ex])
+ assert actual.points_3D == expected.points_3D
+ # Don't assert actual.oid == expected.oid it's defined by
+ # actual.oid indicates the order actual was written in, expected.oid
+ # is not currently encoded (as we'd have to resort the entire Shapefile after each shape)
+ assert actual.parts == expected.parts, f"{type(actual.parts)=}, {type(expected.parts)=}"
+
+ if (m := getattr(actual, "m", None)):
+ assert m == expected.m, f"{type(m)=}, {type(expected.m)=}"
+ else:
+ assert not hasattr(expected, "m")
+
+ if (z := getattr(actual, "z", None)):
+ assert z == expected.z, f"{type(z)=}, {type(expected.z)=}"
+ else:
+ assert not hasattr(expected, "z")
+
+ if (partTypes := getattr(actual, "partTypes", None)):
+ assert actual.partTypes == expected.partTypes, f"{type(actual.partTypes)=}, {type(expected.partTypes)=}"
+ else:
+ assert not hasattr(expected, "partTypes")
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyshp/-/compare/f8d1a3b11e1156b0d9bc7fddb4a64ac7da4aa3d0...9b3fa8355e2993d8b89aeee7f2248002b64c432d
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyshp/-/compare/f8d1a3b11e1156b0d9bc7fddb4a64ac7da4aa3d0...9b3fa8355e2993d8b89aeee7f2248002b64c432d
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20260620/ec8a65f4/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list