Skip to content

Commit 87a1ff5

Browse files
committed
Passes doctests (adjusted to reflect new API: Field, BBox, ZBox & MBox)
1 parent 5f3f0d2 commit 87a1ff5

File tree

2 files changed

+33
-44
lines changed

2 files changed

+33
-44
lines changed

README.md

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ and the bounding box area the shapefile covers:
406406
>>> len(sf)
407407
663
408408
>>> sf.bbox
409-
(-122.515048, 37.652916, -122.327622, 37.863433)
409+
BBox(xmin=-122.515048, ymin=37.652916, xmax=-122.327622, ymax=37.863433)
410410

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

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

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:
558+
* name: the name describing the data at this column index (a string).
559+
* field_type: a FieldType enum member determining the type of data at this column index. Names can be:
560560
* "C": Characters, text.
561561
* "N": Numbers, with or without decimals.
562562
* "F": Floats (same as "N").
563563
* "L": Logical, for boolean True/False values.
564564
* "D": Dates.
565565
* "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
566+
* size: Field length: the length of the data found at this column index. Older GIS
567567
software may truncate this length to 8 or 11 characters for "Character"
568568
fields.
569-
* Decimal length: the number of decimal places found in "Number" fields.
569+
* deci: Decimal length. The number of decimal places found in "Number" fields.
570+
571+
A new field can be created directly from the type enum member etc., or as follows:
572+
573+
>>> shapefile.Field.from_unchecked("Population", "N", 10,0)
574+
Field(name="Population", field_type=FieldType.N, size=10, decimal=0)
575+
576+
Using this method the conversion from string to enum is done automatically.
570577

571578
To see the fields for the Reader object above (sf) call the "fields"
572579
attribute:
573580

574581

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)]
582+
>>> sf.fields
583+
[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)]
595584

596585
The first field of a dbf file is always a 1-byte field called "DeletionFlag",
597586
which indicates records that have been deleted but not removed. However,
@@ -1375,7 +1364,7 @@ Shapefiles containing M-values can be examined in several ways:
13751364
>>> r = shapefile.Reader('shapefiles/test/linem')
13761365

13771366
>>> r.mbox # the lower and upper bound of M-values in the shapefile
1378-
(0.0, 3.0)
1367+
MBox(mmin=0.0, mmax=3.0)
13791368

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

14101399
>>> r.zbox # the lower and upper bound of Z-values in the shapefile
1411-
(0.0, 22.0)
1400+
ZBox(zmin=0.0, zmax=22.0)
14121401

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

src/shapefile.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ class GeoJSONFeatureCollectionWithBBox(GeoJSONFeatureCollection):
327327

328328
# Helpers
329329

330-
MISSING = {None, ""}
330+
MISSING = (None, "") # Don't make a set, as user input may not be Hashable
331331
NODATA = -10e38 # as per the ESRI shapefile spec, only used for m-values.
332332

333333
unpack_2_int32_be = Struct(">2i").unpack
@@ -2538,7 +2538,7 @@ def __record(
25382538
# parse each value
25392539
record = []
25402540
for (__name, typ, __size, decimal), value in zip(fieldTuples, recordContents):
2541-
if typ in {"N", "F"}:
2541+
if typ in {FieldType.N, FieldType.F}:
25422542
# numeric or float: number stored as a string, right justified, and padded with blanks to the width of the field.
25432543
value = value.split(b"\0")[0]
25442544
value = value.replace(b"*", b"") # QGIS NULL is all '*' chars
@@ -2564,7 +2564,7 @@ def __record(
25642564
except ValueError:
25652565
# not parseable as int, set to None
25662566
value = None
2567-
elif typ == "D":
2567+
elif typ is FieldType.D:
25682568
# date: 8 bytes - date stored as a string in the format YYYYMMDD.
25692569
if (
25702570
not value.replace(b"\x00", b"")
@@ -2582,7 +2582,7 @@ def __record(
25822582
except (TypeError, ValueError):
25832583
# if invalid date, just return as unicode string so user can decimalde
25842584
value = str(value.strip())
2585-
elif typ == "L":
2585+
elif typ is FieldType.L:
25862586
# logical: 1 byte - initialized to 0x20 (space) otherwise T or F.
25872587
if value == b" ":
25882588
value = None # space means missing or not yet set
@@ -3225,7 +3225,7 @@ def __shxRecord(self, offset: int, length: int) -> None:
32253225

32263226
def record(
32273227
self,
3228-
*recordList: list[RecordValue],
3228+
*recordList: RecordValue,
32293229
**recordDict: RecordValue,
32303230
) -> None:
32313231
"""Creates a dbf attribute record. You can submit either a sequence of
@@ -3241,7 +3241,7 @@ def record(
32413241
record: list[RecordValue]
32423242
fieldCount = sum(1 for field in self.fields if field[0] != "DeletionFlag")
32433243
if recordList:
3244-
record = list(*recordList)
3244+
record = list(recordList)
32453245
while len(record) < fieldCount:
32463246
record.append("")
32473247
elif recordDict:
@@ -3272,7 +3272,7 @@ def _dbf_missing_placeholder(
32723272
return "0" * 8 # QGIS NULL for date type
32733273
if field_type is FieldType.L:
32743274
return " "
3275-
return str(value)
3275+
return str(value)[:size].ljust(size)
32763276

32773277
@overload
32783278
@staticmethod
@@ -3362,20 +3362,20 @@ def __dbfRecord(self, record: list[RecordValue]) -> None:
33623362
str_val = self._try_coerce_to_logical_str(value)
33633363
else:
33643364
if isinstance(value, bytes):
3365-
decoded_val = value.decode(self.encoding, self.encodingErrors)
3365+
str_val = value.decode(self.encoding, self.encodingErrors)
33663366
else:
33673367
# anything else is forced to string.
3368-
decoded_val = str(value)
3369-
# Truncate to the length of the field
3370-
str_val = decoded_val[:size].ljust(size)
3368+
str_val = str(value)
33713369

3372-
# should be default ascii encoding
3373-
encoded_val = str_val.encode("ascii", self.encodingErrors)
3370+
# Truncate or right pad to the length of the field
3371+
encoded_val = str_val.encode(self.encoding, self.encodingErrors)[
3372+
:size
3373+
].ljust(size)
33743374

33753375
if len(encoded_val) != size:
33763376
raise ShapefileException(
3377-
"Shapefile Writer unable to pack incorrect sized value"
3378-
f" (size {len(encoded_val)}) into field '{fieldName}' (size {size})."
3377+
f"Shapefile Writer unable to pack incorrect sized {value=!r} "
3378+
f"(size {len(encoded_val)}) into field '{fieldName}' (size {size})."
33793379
)
33803380
f.write(encoded_val)
33813381

@@ -3674,7 +3674,7 @@ def _test(args: list[str] = sys.argv[1:], verbosity: bool = False) -> int:
36743674
new_url = _replace_remote_url(old_url)
36753675
example.source = example.source.replace(old_url, new_url)
36763676

3677-
runner = doctest.DocTestRunner(verbose=verbosity)
3677+
runner = doctest.DocTestRunner(verbose=verbosity, optionflags=doctest.FAIL_FAST)
36783678

36793679
if verbosity == 0:
36803680
print(f"Running {len(tests.examples)} doctests...")

0 commit comments

Comments
 (0)