Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.1.3
- **Date**: 25th June 2026
- **Version**: 3.1.4.dev
- **Date**: 27th June 2026
- **License**: [MIT](https://github.com/GeospatialPython/pyshp/blob/master/LICENSE.TXT)

## Contents
Expand Down Expand Up @@ -93,6 +93,9 @@ part of your geospatial project.

# Version Changes

## 3.1.4.dev
### Testing
- Test other codecs (ascii and unicode so far).
## 3.1.3
- Restore faster text writing paths for single-byte Ascii encodings, and Utf-8.

Expand Down
6 changes: 6 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@

VERSION 3.1.4.dev

2026-06-27
* Test other codecs (ascii and unicode so far).

VERSION 3.1.3

2026-06-25
Expand Down
30 changes: 16 additions & 14 deletions src/shapefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from __future__ import annotations

__version__ = "3.1.3"
__version__ = "3.1.4.dev"

import abc
import array
Expand Down Expand Up @@ -283,6 +283,7 @@ def _truncate_utf8_str(
)


@functools.cache
def _BOM_and_dbf_decoded_pad_bytes(
encoding: str = "utf8",
) -> tuple[bytes, Mapping[str, bytes]]:
Expand Down Expand Up @@ -310,7 +311,6 @@ def _encode_dbf_string(
size: int,
decode: Decoder | None,
pad_byte: bytes,
decoded_pad_bytes: Mapping[str, bytes],
encoding: str = "utf8",
encodingErrors: str = "strict",
strict: bool = True,
Expand Down Expand Up @@ -358,6 +358,8 @@ def _encode_dbf_string(
f"to a short enough byte string, using {encoding=}, {encodingErrors=} ({BOM=!r})"
)

_BOM, decoded_pad_bytes = _BOM_and_dbf_decoded_pad_bytes(encoding)

for suffix, pad_bytes in decoded_pad_bytes.items():
if s.endswith(suffix):
msg = (
Expand Down Expand Up @@ -523,7 +525,6 @@ def from_unchecked(
cls,
name: str,
*,
decoded_pad_bytes: Mapping[str, bytes],
field_type: str | bytes | FieldTypeT = "C",
size: int = 50,
decimal: int = 0,
Expand All @@ -532,6 +533,8 @@ def from_unchecked(
strict: bool = False,
) -> Field:

name = str(name)

if "\x00" in name:
msg = (
"Field names should not contain null characters "
Expand Down Expand Up @@ -567,11 +570,10 @@ def from_unchecked(
# Only use the portion of the name that we are able to encode to
# 10 bytes or less.
_encoded_name, trimmed_name = cls.trim_name_until_encodable(
name=str(name),
name=name,
encoding=encoding,
encodingErrors=encodingErrors,
strict=strict,
decoded_pad_bytes=decoded_pad_bytes,
)

# A doctest in README.md previously passed in a string ('40') for size,
Expand All @@ -586,7 +588,6 @@ def from_unchecked(
encoding=encoding,
encodingErrors=encodingErrors,
strict=strict,
decoded_pad_bytes=decoded_pad_bytes,
)
return inst

Expand All @@ -597,14 +598,12 @@ def trim_name_until_encodable(
encoding: str = "utf8",
encodingErrors: str = "strict",
strict: bool = False,
decoded_pad_bytes: Mapping[str, bytes] = {},
) -> tuple[bytes, str]:
return _encode_dbf_string(
s=name,
size=10,
decode=cls.decode_name,
pad_byte=b"\x00",
decoded_pad_bytes=decoded_pad_bytes,
encoding=encoding,
encodingErrors=encodingErrors,
strict=strict,
Expand All @@ -615,7 +614,6 @@ def encode_field_descriptor(
encoding: str = "utf8",
encodingErrors: str = "strict",
strict: bool = False,
decoded_pad_bytes: Mapping[str, bytes] = {},
) -> bytes:
# encoded_name = self.name.encode(encoding, encodingErrors)
# encoded_name = encoded_name[:10].ljust(10, b"\x00")
Expand All @@ -624,7 +622,6 @@ def encode_field_descriptor(
encoding=encoding,
encodingErrors=encodingErrors,
strict=strict,
decoded_pad_bytes=decoded_pad_bytes,
)

encoded_field_type = self.field_type.encode("ascii")
Expand Down Expand Up @@ -4242,7 +4239,6 @@ def field(
encoding=self.encoding,
encodingErrors=self.encodingErrors,
strict=self.strict,
decoded_pad_bytes=self._decoded_pad_bytes,
)
self.fields.append(field)

Expand Down Expand Up @@ -4287,7 +4283,6 @@ def _header(self) -> None:
encoding=self.encoding,
encodingErrors=self.encodingErrors,
strict=self.strict,
decoded_pad_bytes=self._decoded_pad_bytes,
)
)

Expand Down Expand Up @@ -4454,7 +4449,7 @@ def _record(self, record: list[RecordValue]) -> None:
)
if self.strict:
raise DbfStringDataLoss(msg)
warnings.warn(msg)
warnings.warn(msg, category=PossibleDataLoss)

encoded = encoded.ljust(size)
else:
Expand All @@ -4463,7 +4458,6 @@ def _record(self, record: list[RecordValue]) -> None:
size=size,
decode=_decode_C_or_M_field if self.strict else None,
pad_byte=b" ",
decoded_pad_bytes=self._decoded_pad_bytes,
encoding=self.encoding,
encodingErrors=self.encodingErrors,
strict=self.strict,
Expand Down Expand Up @@ -4886,6 +4880,14 @@ def fields(self) -> list[Field]:
def fields(self, value: list[Field]) -> None:
self.dbf_writer.fields = value

@property
def strict(self) -> bool:
return self.dbf_writer.strict

@strict.setter
def strict(self, value: bool) -> None:
self.dbf_writer.strict = value

@property
def recNum(self) -> int:
if not self._dbf_writer:
Expand Down
Loading
Loading