diff --git a/geojson_pydantic/features.py b/geojson_pydantic/features.py index 089193e..8c8185a 100644 --- a/geojson_pydantic/features.py +++ b/geojson_pydantic/features.py @@ -1,11 +1,14 @@ """pydantic models for GeoJSON Feature objects.""" +from __future__ import annotations + from typing import Any, Dict, Generic, Iterator, List, Literal, Optional, TypeVar, Union from pydantic import BaseModel, Field, StrictInt, StrictStr, field_validator from geojson_pydantic.base import _GeoJsonBase from geojson_pydantic.geometries import Geometry +from geojson_pydantic.types import BBox Props = TypeVar("Props", bound=Union[Dict[str, Any], BaseModel]) Geom = TypeVar("Geom", bound=Geometry) @@ -29,6 +32,26 @@ def set_geometry(cls, geometry: Any) -> Any: return geometry + @staticmethod + def make( + *, + type: Literal["Feature"] = "Feature", + geometry: Optional[Geom] = None, + properties: Optional[Props] = None, + id: Optional[Union[StrictInt, StrictStr]] = None, + bbox: Optional[BBox] = None, + ) -> Feature: + """Allow to create a Feature without needint to specify all arguments. + In particular it is not necessary to specify the redundant `type="Feature"`. + """ + return Feature( + type=type, + geometry=geometry, + properties=properties, + id=id, + bbox=bbox, + ) + Feat = TypeVar("Feat", bound=Feature) diff --git a/tests/test_features.py b/tests/test_features.py index a4f7846..ee891d5 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -59,6 +59,9 @@ class GenericProperties(BaseModel): "bbox": [13.38272, 52.46385, 13.42786, 52.48445], } +test_feature_no_type: Dict[str, Any] = test_feature.copy() +test_feature_no_type.pop("type") + test_feature_geom_null: Dict[str, Any] = { "type": "Feature", "geometry": None, @@ -229,6 +232,17 @@ def test_bad_feature_id(id): Feature(**test_feature, id=id) +def test_feature_make(): + # Using make it is possible to create a valid Feature without specifying + # redundant information, e.g. `type="Feature"`: + feature = Feature.make() + assert feature.type == "Feature" + feature = Feature.make(properties={"foo": "bar"}) + assert feature.type == "Feature" + assert feature.geometry is None + assert feature.properties == {"foo": "bar"} + + def test_feature_validation(): """Test default.""" assert Feature(type="Feature", properties=None, geometry=None) @@ -283,6 +297,13 @@ def test_feature_validation(): ) +def test_deserialization(): + Feature.model_validate(test_feature) + # type missing + with pytest.raises(ValidationError): + Feature.model_validate(test_feature_no_type) + + def test_bbox_validation(): # Some attempts at generic validation did not validate the types within # bbox before passing them to the function and resulted in TypeErrors.