From 628e7819f6d5b7347ac39c5d8ee7482c9bd49810 Mon Sep 17 00:00:00 2001 From: Sean Gillies Date: Tue, 23 Apr 2024 09:31:31 -0600 Subject: [PATCH] Give model classes a nice repr again (#1380) * Give model classes a nice repr again Resolves #1379 * Limit representations of dicts * Remove redundant methods, consolidating in base class * Dict item access yields more dicts * Use object access in topojson test --- CHANGES.txt | 7 +++++++ fiona/model.py | 26 ++++++++++++++++++++++++-- tests/test_model.py | 9 +++++++++ tests/test_topojson.py | 6 +++--- 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 516ced0ca..f19fcd1b3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,6 +3,13 @@ Changes All issue numbers are relative to https://github.com/Toblerity/Fiona/issues. +1.10b2 (TBD) +------------ + +Bug fixes: + +- Fiona model objects have a informative, printable representation again (#). + 1.10b1 (2024-04-16) ------------------- diff --git a/fiona/model.py b/fiona/model.py index 4e575f050..fb246c7d4 100644 --- a/fiona/model.py +++ b/fiona/model.py @@ -5,10 +5,15 @@ from enum import Enum import itertools from json import JSONEncoder +import reprlib from warnings import warn from fiona.errors import FionaDeprecationWarning +_model_repr = reprlib.Repr() +_model_repr.maxlist = 1 +_model_repr.maxdict = 5 + class OGRGeometryType(Enum): Unknown = 0 @@ -134,7 +139,10 @@ def _props(self): } def __getitem__(self, item): - props = self._props() + props = { + k: (dict(v) if isinstance(v, Object) else v) + for k, v in self._props().items() + } props.update(**self._data) return props[item] @@ -146,6 +154,13 @@ def __len__(self): props = self._props() return len(props) + len(self._data) + def __repr__(self): + kvs = [ + f"{k}={v!r}" + for k, v in itertools.chain(self._props().items(), self._data.items()) + ] + return "fiona.{}({})".format(self.__class__.__name__, ", ".join(kvs)) + def __setitem__(self, key, value): warn( "instances of this class -- CRS, geometry, and feature objects -- will become immutable in fiona version 2.0", @@ -197,6 +212,10 @@ def __init__(self, coordinates=None, type=None, geometries=None, **data): ) super().__init__(**data) + def __repr__(self): + kvs = [f"{k}={_model_repr.repr(v)}" for k, v in self.items() if v is not None] + return "fiona.Geometry({})".format(", ".join(kvs)) + @classmethod def from_dict(cls, ob=None, **kwargs): if ob is not None: @@ -384,7 +403,10 @@ class ObjectEncoder(JSONEncoder): def default(self, o): if isinstance(o, Object): - o_dict = {k: self.default(v) for k, v in o.items()} + o_dict = { + k: self.default(v) + for k, v in itertools.chain(o._props().items(), o._data.items()) + } if isinstance(o, Geometry): if o.type == "GeometryCollection": _ = o_dict.pop("coordinates", None) diff --git a/tests/test_model.py b/tests/test_model.py index cd3082c69..4a3825c7a 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -333,3 +333,12 @@ def test_geometry_collection_encoding(): assert "coordinates" not in ObjectEncoder().default( Geometry(type="GeometryCollection", geometries=[]) ) + + +def test_feature_repr(): + feat = Feature( + id="1", + geometry=Geometry(type="LineString", coordinates=[(0, 0)] * 100), + properties=Properties(a=1, foo="bar"), + ) + assert repr(feat) == "fiona.Feature(geometry=fiona.Geometry(coordinates=[(0, 0), ...], type='LineString'), id='1', properties=fiona.Properties(a=1, foo='bar'))" diff --git a/tests/test_topojson.py b/tests/test_topojson.py index e9dd33cfc..24384138c 100644 --- a/tests/test_topojson.py +++ b/tests/test_topojson.py @@ -32,6 +32,6 @@ def test_read_topojson(data_dir): assert len(features) == 3, "unexpected number of features" for feature in features: - assert isinstance(feature["properties"], Properties) - assert len(feature["properties"]) > 0 - assert feature["geometry"]["type"] in {"Point", "LineString", "Polygon"} + assert isinstance(feature.properties, Properties) + assert len(feature.properties) > 0 + assert feature.geometry.type in {"Point", "LineString", "Polygon"}