Skip to content

Commit b5f4bf8

Browse files
authored
Merge pull request #343 from JamesParrott/pyshp-3.0.0-alpha-the-types-awaken
Pyshp 3.0.0 alpha the types awaken
2 parents 6ca0674 + c920304 commit b5f4bf8

File tree

5 files changed

+608
-357
lines changed

5 files changed

+608
-357
lines changed

README.md

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ The Python Shapefile Library (PyShp) reads and writes ESRI Shapefiles in pure Py
88

99
- **Author**: [Joel Lawhead](https://github.com/GeospatialPython)
1010
- **Maintainers**: [Karim Bahgat](https://github.com/karimbahgat)
11-
- **Version**: 2.3.1
12-
- **Date**: 28 July, 2022
11+
- **Version**: 3.0.0-alpha
12+
- **Date**: 31 July, 2025
1313
- **License**: [MIT](https://github.com/GeospatialPython/pyshp/blob/master/LICENSE.TXT)
1414

1515
## Contents
@@ -93,6 +93,30 @@ part of your geospatial project.
9393

9494
# Version Changes
9595

96+
## 3.0.0-alpha
97+
98+
### Breaking Changes:
99+
- Python 2 and Python 3.8 support dropped.
100+
- Field info tuple is now a namedtuple (Field) instead of a list.
101+
- Field type codes are now FieldType enum members.
102+
- bbox, mbox and zbox attributes are all new Namedtuples.
103+
- Writer does not mutate shapes.
104+
- New custom subclasses for each shape type: Null, Multipatch, Point, Polyline,
105+
Multipoint, and Polygon, plus the latter 4's M and Z variants (Reader and
106+
Writer are still compatible with their base class, Shape, as before).
107+
- Shape sub classes are creatable from, and serializable to bytes streams,
108+
as per the shapefile spec.
109+
110+
### Code quality
111+
- Statically typed, and checked with Mypy
112+
- Checked with Ruff.
113+
- f-strings
114+
- Remove Python 2 specific functions.
115+
- Run doctests against wheels.
116+
- Testing of wheels before publishing them
117+
- pyproject.toml src layout
118+
- Slow test marked.
119+
96120
## 2.4.0
97121

98122
### Breaking Change. Support for Python 2 and Pythons <= 3.8 to be dropped.
@@ -406,7 +430,7 @@ and the bounding box area the shapefile covers:
406430
>>> len(sf)
407431
663
408432
>>> sf.bbox
409-
(-122.515048, 37.652916, -122.327622, 37.863433)
433+
BBox(xmin=-122.515048, ymin=37.652916, xmax=-122.327622, ymax=37.863433)
410434

411435
Finally, if you would prefer to work with the entire shapefile in a different
412436
format, you can convert all of it to a GeoJSON dictionary, although you may lose
@@ -553,45 +577,34 @@ in the shp geometry file and the dbf attribute file.
553577

554578
The field names of a shapefile are available as soon as you read a shapefile.
555579
You can call the "fields" attribute of the shapefile as a Python list. Each
556-
field is a Python list with the following information:
580+
field is a Python namedtuple (Field) with the following information:
557581

558-
* Field name: the name describing the data at this column index.
559-
* Field type: the type of data at this column index. Types can be:
582+
* name: the name describing the data at this column index (a string).
583+
* field_type: a FieldType enum member determining the type of data at this column index. Names can be:
560584
* "C": Characters, text.
561585
* "N": Numbers, with or without decimals.
562586
* "F": Floats (same as "N").
563587
* "L": Logical, for boolean True/False values.
564588
* "D": Dates.
565589
* "M": Memo, has no meaning within a GIS and is part of the xbase spec instead.
566-
* Field length: the length of the data found at this column index. Older GIS
590+
* size: Field length: the length of the data found at this column index. Older GIS
567591
software may truncate this length to 8 or 11 characters for "Character"
568592
fields.
569-
* Decimal length: the number of decimal places found in "Number" fields.
593+
* deci: Decimal length. The number of decimal places found in "Number" fields.
594+
595+
A new field can be created directly from the type enum member etc., or as follows:
596+
597+
>>> shapefile.Field.from_unchecked("Population", "N", 10,0)
598+
Field(name="Population", field_type=FieldType.N, size=10, decimal=0)
599+
600+
Using this method the conversion from string to enum is done automatically.
570601

571602
To see the fields for the Reader object above (sf) call the "fields"
572603
attribute:
573604

574605

575-
>>> fields = sf.fields
576-
577-
>>> assert fields == [("DeletionFlag", "C", 1, 0), ["AREA", "N", 18, 5],
578-
... ["BKG_KEY", "C", 12, 0], ["POP1990", "N", 9, 0], ["POP90_SQMI", "N", 10, 1],
579-
... ["HOUSEHOLDS", "N", 9, 0],
580-
... ["MALES", "N", 9, 0], ["FEMALES", "N", 9, 0], ["WHITE", "N", 9, 0],
581-
... ["BLACK", "N", 8, 0], ["AMERI_ES", "N", 7, 0], ["ASIAN_PI", "N", 8, 0],
582-
... ["OTHER", "N", 8, 0], ["HISPANIC", "N", 8, 0], ["AGE_UNDER5", "N", 8, 0],
583-
... ["AGE_5_17", "N", 8, 0], ["AGE_18_29", "N", 8, 0], ["AGE_30_49", "N", 8, 0],
584-
... ["AGE_50_64", "N", 8, 0], ["AGE_65_UP", "N", 8, 0],
585-
... ["NEVERMARRY", "N", 8, 0], ["MARRIED", "N", 9, 0], ["SEPARATED", "N", 7, 0],
586-
... ["WIDOWED", "N", 8, 0], ["DIVORCED", "N", 8, 0], ["HSEHLD_1_M", "N", 8, 0],
587-
... ["HSEHLD_1_F", "N", 8, 0], ["MARHH_CHD", "N", 8, 0],
588-
... ["MARHH_NO_C", "N", 8, 0], ["MHH_CHILD", "N", 7, 0],
589-
... ["FHH_CHILD", "N", 7, 0], ["HSE_UNITS", "N", 9, 0], ["VACANT", "N", 7, 0],
590-
... ["OWNER_OCC", "N", 8, 0], ["RENTER_OCC", "N", 8, 0],
591-
... ["MEDIAN_VAL", "N", 7, 0], ["MEDIANRENT", "N", 4, 0],
592-
... ["UNITS_1DET", "N", 8, 0], ["UNITS_1ATT", "N", 7, 0], ["UNITS2", "N", 7, 0],
593-
... ["UNITS3_9", "N", 8, 0], ["UNITS10_49", "N", 8, 0],
594-
... ["UNITS50_UP", "N", 8, 0], ["MOBILEHOME", "N", 7, 0]]
606+
>>> sf.fields
607+
[Field(name="DeletionFlag", field_type=FieldType.C, size=1, decimal=0), Field(name="AREA", field_type=FieldType.N, size=18, decimal=5), Field(name="BKG_KEY", field_type=FieldType.C, size=12, decimal=0), Field(name="POP1990", field_type=FieldType.N, size=9, decimal=0), Field(name="POP90_SQMI", field_type=FieldType.N, size=10, decimal=1), Field(name="HOUSEHOLDS", field_type=FieldType.N, size=9, decimal=0), Field(name="MALES", field_type=FieldType.N, size=9, decimal=0), Field(name="FEMALES", field_type=FieldType.N, size=9, decimal=0), Field(name="WHITE", field_type=FieldType.N, size=9, decimal=0), Field(name="BLACK", field_type=FieldType.N, size=8, decimal=0), Field(name="AMERI_ES", field_type=FieldType.N, size=7, decimal=0), Field(name="ASIAN_PI", field_type=FieldType.N, size=8, decimal=0), Field(name="OTHER", field_type=FieldType.N, size=8, decimal=0), Field(name="HISPANIC", field_type=FieldType.N, size=8, decimal=0), Field(name="AGE_UNDER5", field_type=FieldType.N, size=8, decimal=0), Field(name="AGE_5_17", field_type=FieldType.N, size=8, decimal=0), Field(name="AGE_18_29", field_type=FieldType.N, size=8, decimal=0), Field(name="AGE_30_49", field_type=FieldType.N, size=8, decimal=0), Field(name="AGE_50_64", field_type=FieldType.N, size=8, decimal=0), Field(name="AGE_65_UP", field_type=FieldType.N, size=8, decimal=0), Field(name="NEVERMARRY", field_type=FieldType.N, size=8, decimal=0), Field(name="MARRIED", field_type=FieldType.N, size=9, decimal=0), Field(name="SEPARATED", field_type=FieldType.N, size=7, decimal=0), Field(name="WIDOWED", field_type=FieldType.N, size=8, decimal=0), Field(name="DIVORCED", field_type=FieldType.N, size=8, decimal=0), Field(name="HSEHLD_1_M", field_type=FieldType.N, size=8, decimal=0), Field(name="HSEHLD_1_F", field_type=FieldType.N, size=8, decimal=0), Field(name="MARHH_CHD", field_type=FieldType.N, size=8, decimal=0), Field(name="MARHH_NO_C", field_type=FieldType.N, size=8, decimal=0), Field(name="MHH_CHILD", field_type=FieldType.N, size=7, decimal=0), Field(name="FHH_CHILD", field_type=FieldType.N, size=7, decimal=0), Field(name="HSE_UNITS", field_type=FieldType.N, size=9, decimal=0), Field(name="VACANT", field_type=FieldType.N, size=7, decimal=0), Field(name="OWNER_OCC", field_type=FieldType.N, size=8, decimal=0), Field(name="RENTER_OCC", field_type=FieldType.N, size=8, decimal=0), Field(name="MEDIAN_VAL", field_type=FieldType.N, size=7, decimal=0), Field(name="MEDIANRENT", field_type=FieldType.N, size=4, decimal=0), Field(name="UNITS_1DET", field_type=FieldType.N, size=8, decimal=0), Field(name="UNITS_1ATT", field_type=FieldType.N, size=7, decimal=0), Field(name="UNITS2", field_type=FieldType.N, size=7, decimal=0), Field(name="UNITS3_9", field_type=FieldType.N, size=8, decimal=0), Field(name="UNITS10_49", field_type=FieldType.N, size=8, decimal=0), Field(name="UNITS50_UP", field_type=FieldType.N, size=8, decimal=0), Field(name="MOBILEHOME", field_type=FieldType.N, size=7, decimal=0)]
595608

596609
The first field of a dbf file is always a 1-byte field called "DeletionFlag",
597610
which indicates records that have been deleted but not removed. However,
@@ -919,8 +932,8 @@ You can also add attributes using keyword arguments where the keys are field nam
919932

920933

921934
>>> w = shapefile.Writer('shapefiles/test/dtype')
922-
>>> w.field('FIRST_FLD','C','40')
923-
>>> w.field('SECOND_FLD','C','40')
935+
>>> w.field('FIRST_FLD','C', 40)
936+
>>> w.field('SECOND_FLD','C', 40)
924937
>>> w.null()
925938
>>> w.null()
926939
>>> w.record('First', 'Line')
@@ -1375,7 +1388,7 @@ Shapefiles containing M-values can be examined in several ways:
13751388
>>> r = shapefile.Reader('shapefiles/test/linem')
13761389

13771390
>>> r.mbox # the lower and upper bound of M-values in the shapefile
1378-
[0.0, 3.0]
1391+
MBox(mmin=0.0, mmax=3.0)
13791392

13801393
>>> r.shape(0).m # flat list of M-values
13811394
[0.0, None, 3.0, None, 0.0, None, None]
@@ -1408,7 +1421,7 @@ To examine a Z-type shapefile you can do:
14081421
>>> r = shapefile.Reader('shapefiles/test/linez')
14091422

14101423
>>> r.zbox # the lower and upper bound of Z-values in the shapefile
1411-
[0.0, 22.0]
1424+
ZBox(zmin=0.0, zmax=22.0)
14121425

14131426
>>> r.shape(0).z # flat list of Z-values
14141427
[18.0, 20.0, 22.0, 0.0, 0.0, 0.0, 0.0, 15.0, 13.0, 14.0]

changelog.txt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
VERSION 3.0.0-alpha
22

3-
Python 2 and Python 3.8 support dropped
3+
Breaking Changes:
4+
* Python 2 and Python 3.8 support dropped.
5+
* Field info tuple is now a namedtuple (Field) instead of a list.
6+
* Field type codes are now FieldType enum members.
7+
* bbox, mbox and zbox attributes are all new Namedtuples.
8+
* Writer does not mutate shapes.
9+
* New custom subclasses for each shape type: Null, Multipatch, Point, Polyline,
10+
Multipoint, and Polygon, plus the latter 4's M and Z variants (Reader and
11+
Writer are still compatible with their base class, Shape, as before).
12+
* Shape sub classes are creatable from, and serializable to bytes streams,
13+
as per the shapefile spec.
414

5-
2025-07-22
615
Code quality
16+
* Statically typed and checked with Mypy
17+
* Checked with Ruff.
718
* Type hints
819
* f-strings
920
* Remove Python 2 specific functions.

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ classifiers = [
2626
"Topic :: Software Development :: Libraries",
2727
"Topic :: Software Development :: Libraries :: Python Modules",
2828
]
29+
dependencies = [
30+
"typing_extensions",
31+
]
2932

3033
[project.optional-dependencies]
3134
test = ["pytest"]

0 commit comments

Comments
 (0)