Skip to content

Commit cecd827

Browse files
committed
update from main
2 parents 92d2b5d + d65cdc0 commit cecd827

File tree

7 files changed

+109
-52
lines changed

7 files changed

+109
-52
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
18+
python-version: ['3.8', '3.9', '3.10', '3.11']
1919

2020
steps:
2121
- uses: actions/checkout@v3

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [0.6.0] - TBD
9+
10+
- Remove python 3.7 support
11+
- Enforce required keys and avoid defaults. This aim to follow the geojson specification to the letter.
12+
13+
```python
14+
# Before
15+
Feature(geometry=Point(coordinates=(0,0)))
16+
17+
# Now
18+
Feature(
19+
type="Feature",
20+
geometry=Point(
21+
type="Point",
22+
coordinates=(0,0)
23+
),
24+
properties=None,
25+
)
26+
```
27+
28+
### Fixed
29+
30+
- Do not validates arbitrary dictionaries. Make `Type` a mandatory key for objects (https://github.com/developmentseed/geojson-pydantic/pull/94)
31+
832
## [0.5.0] - 2022-12-16
933

1034
### Added

geojson_pydantic/features.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""pydantic models for GeoJSON Feature objects."""
22

3-
from typing import Any, Dict, Generic, Iterator, List, Optional, TypeVar, Union
3+
from typing import Any, Dict, Generic, Iterator, List, Literal, Optional, TypeVar, Union
44

55
from pydantic import BaseModel, Field, validator
66
from pydantic.generics import GenericModel
@@ -15,9 +15,9 @@
1515
class Feature(GenericModel, Generic[Geom, Props]):
1616
"""Feature Model"""
1717

18-
type: str = Field(default="Feature", const=True)
19-
geometry: Optional[Geom] = None
20-
properties: Optional[Props] = None
18+
type: Literal["Feature"]
19+
geometry: Union[Geom, None] = Field(...)
20+
properties: Union[Props, None] = Field(...)
2121
id: Optional[str] = None
2222
bbox: Optional[BBox] = None
2323

@@ -31,6 +31,7 @@ def set_geometry(cls, geometry: Any) -> Any:
3131
"""set geometry from geo interface or input"""
3232
if hasattr(geometry, "__geo_interface__"):
3333
return geometry.__geo_interface__
34+
3435
return geometry
3536

3637
@property
@@ -44,23 +45,22 @@ def __geo_interface__(self) -> Dict[str, Any]:
4445
"geometry": self.geometry.__geo_interface__
4546
if self.geometry is not None
4647
else None,
48+
"properties": self.properties,
4749
}
50+
4851
if self.bbox:
4952
geo["bbox"] = self.bbox
5053

5154
if self.id:
5255
geo["id"] = self.id
5356

54-
if self.properties:
55-
geo["properties"] = self.properties
56-
5757
return geo
5858

5959

6060
class FeatureCollection(GenericModel, Generic[Geom, Props]):
6161
"""FeatureCollection Model"""
6262

63-
type: str = Field(default="FeatureCollection", const=True)
63+
type: Literal["FeatureCollection"]
6464
features: List[Feature[Geom, Props]]
6565
bbox: Optional[BBox] = None
6666

geojson_pydantic/geometries.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""pydantic models for GeoJSON Geometry objects."""
22

33
import abc
4-
from typing import Any, Dict, Iterator, List, Union
4+
from typing import Any, Dict, Iterator, List, Literal, Union
55

6-
from pydantic import BaseModel, Field, ValidationError, validator
6+
from pydantic import BaseModel, ValidationError, validator
77
from pydantic.error_wrappers import ErrorWrapper
88

99
from geojson_pydantic.types import (
@@ -75,7 +75,7 @@ def wkt(self) -> str:
7575
class Point(_GeometryBase):
7676
"""Point Model"""
7777

78-
type: str = Field(default="Point", const=True)
78+
type: Literal["Point"]
7979
coordinates: Position
8080

8181
@property
@@ -90,7 +90,7 @@ def _wkt_inset(self) -> str:
9090
class MultiPoint(_GeometryBase):
9191
"""MultiPoint Model"""
9292

93-
type: str = Field(default="MultiPoint", const=True)
93+
type: Literal["MultiPoint"]
9494
coordinates: MultiPointCoords
9595

9696
@property
@@ -105,7 +105,7 @@ def _wkt_coordinates(self) -> str:
105105
class LineString(_GeometryBase):
106106
"""LineString Model"""
107107

108-
type: str = Field(default="LineString", const=True)
108+
type: Literal["LineString"]
109109
coordinates: LineStringCoords
110110

111111
@property
@@ -120,7 +120,7 @@ def _wkt_coordinates(self) -> str:
120120
class MultiLineString(_GeometryBase):
121121
"""MultiLineString Model"""
122122

123-
type: str = Field(default="MultiLineString", const=True)
123+
type: Literal["MultiLineString"]
124124
coordinates: MultiLineStringCoords
125125

126126
@property
@@ -147,7 +147,7 @@ def check_closure(cls, coordinates: List) -> List:
147147
class Polygon(_GeometryBase):
148148
"""Polygon Model"""
149149

150-
type: str = Field(default="Polygon", const=True)
150+
type: Literal["Polygon"]
151151
coordinates: PolygonCoords
152152

153153
@validator("coordinates")
@@ -184,16 +184,17 @@ def from_bounds(
184184
) -> "Polygon":
185185
"""Create a Polygon geometry from a boundingbox."""
186186
return cls(
187+
type="Polygon",
187188
coordinates=[
188189
[(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax), (xmin, ymin)]
189-
]
190+
],
190191
)
191192

192193

193194
class MultiPolygon(_GeometryBase):
194195
"""MultiPolygon Model"""
195196

196-
type: str = Field(default="MultiPolygon", const=True)
197+
type: Literal["MultiPolygon"]
197198
coordinates: MultiPolygonCoords
198199

199200
@property
@@ -213,7 +214,7 @@ def _wkt_coordinates(self) -> str:
213214
class GeometryCollection(BaseModel):
214215
"""GeometryCollection Model"""
215216

216-
type: str = Field(default="GeometryCollection", const=True)
217+
type: Literal["GeometryCollection"]
217218
geometries: List[Geometry]
218219

219220
def __iter__(self) -> Iterator[Geometry]: # type: ignore [override]

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "geojson-pydantic"
33
description = "Pydantic data models for the GeoJSON spec."
44
readme = "README.md"
5-
requires-python = ">=3.7"
5+
requires-python = ">=3.8"
66
license = {file = "LICENSE"}
77
authors = [
88
{name = "Drew Bollinger", email = "[email protected]"},
@@ -12,7 +12,6 @@ classifiers = [
1212
"Intended Audience :: Information Technology",
1313
"Intended Audience :: Science/Research",
1414
"License :: OSI Approved :: MIT License",
15-
"Programming Language :: Python :: 3.7",
1615
"Programming Language :: Python :: 3.8",
1716
"Programming Language :: Python :: 3.9",
1817
"Programming Language :: Python :: 3.10",

tests/test_features.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,18 @@ class GenericProperties(BaseModel):
6666

6767
def test_feature_collection_iteration():
6868
"""test if feature collection is iterable"""
69-
gc = FeatureCollection(features=[test_feature, test_feature])
69+
gc = FeatureCollection(
70+
type="FeatureCollection", features=[test_feature, test_feature]
71+
)
7072
assert hasattr(gc, "__geo_interface__")
7173
iter(gc)
7274

7375

7476
def test_geometry_collection_iteration():
7577
"""test if feature collection is iterable"""
76-
gc = FeatureCollection(features=[test_feature_geometry_collection])
78+
gc = FeatureCollection(
79+
type="FeatureCollection", features=[test_feature_geometry_collection]
80+
)
7781
assert hasattr(gc, "__geo_interface__")
7882
iter(gc)
7983

@@ -152,7 +156,7 @@ def test_generic_properties_should_raise_for_string():
152156

153157
def test_feature_collection_generic():
154158
fc = FeatureCollection[Polygon, GenericProperties](
155-
features=[test_feature, test_feature]
159+
type="FeatureCollection", features=[test_feature, test_feature]
156160
)
157161
assert len(fc) == 2
158162
assert type(fc[0].properties) == GenericProperties
@@ -163,7 +167,7 @@ def test_geo_interface_protocol():
163167
class Pointy:
164168
__geo_interface__ = {"type": "Point", "coordinates": (0.0, 0.0)}
165169

166-
feat = Feature(geometry=Pointy())
170+
feat = Feature(type="Feature", geometry=Pointy(), properties={})
167171
assert feat.geometry.dict() == Pointy.__geo_interface__
168172

169173

@@ -178,7 +182,30 @@ def test_feature_geo_interface_with_null_geometry():
178182

179183

180184
def test_feature_collection_geo_interface_with_null_geometry():
181-
fc = FeatureCollection(features=[test_feature_geom_null, test_feature])
185+
fc = FeatureCollection(
186+
type="FeatureCollection", features=[test_feature_geom_null, test_feature]
187+
)
182188
assert "bbox" not in fc.__geo_interface__
183189
assert "bbox" not in fc.__geo_interface__["features"][0]
184190
assert "bbox" in fc.__geo_interface__["features"][1]
191+
192+
193+
def test_feature_validation():
194+
"""Test default."""
195+
assert Feature(type="Feature", properties=None, geometry=None)
196+
197+
with pytest.raises(ValidationError):
198+
# should be type=Feature
199+
Feature(type="feature", properties=None, geometry=None)
200+
201+
with pytest.raises(ValidationError):
202+
# missing type
203+
Feature(properties=None, geometry=None)
204+
205+
with pytest.raises(ValidationError):
206+
# missing properties
207+
Feature(type="Feature", geometry=None)
208+
209+
with pytest.raises(ValidationError):
210+
# missing geometry
211+
Feature(type="Feature", properties=None)

0 commit comments

Comments
 (0)