diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f96a34ae..5368aefe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - `generate_subcatalogs` can include multiple template values in a single subfolder layer ([#595](https://github.com/stac-utils/pystac/pull/595)) - Avoid implicit re-exports ([#591](https://github.com/stac-utils/pystac/pull/591)) +- Regression where string `Enum` values were not serialized properly in methods like `Link.to_dict` ([#654](https://github.com/stac-utils/pystac/pull/654)) ### Deprecated diff --git a/pystac/catalog.py b/pystac/catalog.py index a76ffef38..d22c3043b 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -1,6 +1,5 @@ import os from copy import deepcopy -from enum import Enum from pystac.errors import STACTypeError from typing import ( Any, @@ -29,7 +28,12 @@ identify_stac_object, migrate_to_latest, ) -from pystac.utils import is_absolute_href, make_absolute_href, make_relative_href +from pystac.utils import ( + StringEnum, + is_absolute_href, + make_absolute_href, + make_relative_href, +) if TYPE_CHECKING: from pystac.asset import Asset as Asset_Type @@ -37,7 +41,7 @@ from pystac.collection import Collection as Collection_Type -class CatalogType(str, Enum): +class CatalogType(StringEnum): SELF_CONTAINED = "SELF_CONTAINED" """A 'self-contained catalog' is one that is designed for portability. Users may want to download an online catalog from and be able to use it on their diff --git a/pystac/extensions/datacube.py b/pystac/extensions/datacube.py index 0a374fcab..1eac0bbdf 100644 --- a/pystac/extensions/datacube.py +++ b/pystac/extensions/datacube.py @@ -4,7 +4,6 @@ """ from abc import ABC -from enum import Enum from typing import Any, Dict, Generic, List, Optional, TypeVar, Union, cast import pystac @@ -13,7 +12,7 @@ PropertiesExtension, ) from pystac.extensions.hooks import ExtensionHooks -from pystac.utils import get_required +from pystac.utils import StringEnum, get_required T = TypeVar("T", pystac.Collection, pystac.Item, pystac.Asset) @@ -42,14 +41,14 @@ VAR_UNIT_PROP = "unit" -class DimensionType(str, Enum): +class DimensionType(StringEnum): """Dimension object types for spatial and temporal Dimension Objects.""" SPATIAL = "spatial" TEMPORAL = "temporal" -class HorizontalSpatialDimensionAxis(str, Enum): +class HorizontalSpatialDimensionAxis(StringEnum): """Allowed values for ``axis`` field of :class:`HorizontalSpatialDimension` object.""" @@ -57,7 +56,7 @@ class HorizontalSpatialDimensionAxis(str, Enum): Y = "y" -class VerticalSpatialDimensionAxis(str, Enum): +class VerticalSpatialDimensionAxis(StringEnum): """Allowed values for ``axis`` field of :class:`VerticalSpatialDimension` object.""" @@ -407,7 +406,7 @@ def reference_system(self, v: Optional[Union[str, float, Dict[str, Any]]]) -> No self.properties[DIM_REF_SYS_PROP] = v -class VariableType(str, Enum): +class VariableType(StringEnum): """Variable object types""" DATA = "data" diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 5a1817d60..d4af7810f 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -3,7 +3,6 @@ https://github.com/stac-extensions/file """ -from enum import Enum from typing import Any, Dict, List, Optional, Union import pystac @@ -14,7 +13,7 @@ STACJSONDescription, STACVersionID, ) -from pystac.utils import get_required +from pystac.utils import StringEnum, get_required SCHEMA_URI = "https://stac-extensions.github.io/file/v2.0.0/schema.json" @@ -26,7 +25,7 @@ VALUES_PROP = PREFIX + "values" -class ByteOrder(str, Enum): +class ByteOrder(StringEnum): """List of allows values for the ``"file:byte_order"`` field defined by the :stac-ext:`File Info Extension `.""" diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index ea2791cb3..0a8af0dda 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -3,14 +3,13 @@ https://github.com/stac-extensions/label """ -from enum import Enum from pystac.extensions.base import ExtensionManagementMixin, SummariesExtension from typing import Any, Dict, Iterable, List, Optional, Sequence, Union, cast import pystac from pystac.serialization.identify import STACJSONDescription, STACVersionID from pystac.extensions.hooks import ExtensionHooks -from pystac.utils import get_required, map_opt +from pystac.utils import StringEnum, get_required, map_opt SCHEMA_URI = "https://stac-extensions.github.io/label/v1.0.0/schema.json" @@ -25,7 +24,7 @@ OVERVIEWS_PROP = PREFIX + "overviews" -class LabelRelType(str, Enum): +class LabelRelType(StringEnum): """A list of rel types defined in the Label Extension. See the :stac-ext:`Label Extension Links ` @@ -36,7 +35,7 @@ class LabelRelType(str, Enum): """Used to indicate a link to the source item to which a label item applies.""" -class LabelType(str, Enum): +class LabelType(StringEnum): """Enumerates valid label types ("raster" or "vector").""" VECTOR = "vector" @@ -46,7 +45,7 @@ class LabelType(str, Enum): """Convenience attribute for checking if values are valid label types""" -class LabelTask(str, Enum): +class LabelTask(StringEnum): """Enumerates recommended values for "label:tasks" field.""" REGRESSION = "regression" @@ -55,7 +54,7 @@ class LabelTask(str, Enum): SEGMENTATION = "segmentation" -class LabelMethod(str, Enum): +class LabelMethod(StringEnum): """Enumerates recommended values for "label:methods" field.""" AUTOMATED = "automated" diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index e32029e72..67b2f0b01 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -2,7 +2,6 @@ https://github.com/stac-extensions/pointcloud """ -from enum import Enum from typing import Any, Dict, Generic, List, Optional, TypeVar, cast, Union import pystac @@ -13,7 +12,7 @@ ) from pystac.extensions.hooks import ExtensionHooks from pystac.summaries import RangeSummary -from pystac.utils import map_opt, get_required +from pystac.utils import StringEnum, map_opt, get_required T = TypeVar("T", pystac.Item, pystac.Asset) @@ -28,7 +27,7 @@ STATISTICS_PROP = PREFIX + "statistics" -class PhenomenologyType(str, Enum): +class PhenomenologyType(StringEnum): """Valid values for the ``pc:type`` field in the :stac-ext:`Pointcloud Item Properties `.""" @@ -39,7 +38,7 @@ class PhenomenologyType(str, Enum): OTHER = "other" -class SchemaType(str, Enum): +class SchemaType(StringEnum): """Valid values for the ``type`` field in a :stac-ext:`Schema Object `.""" diff --git a/pystac/extensions/raster.py b/pystac/extensions/raster.py index 28077595a..9a1b62ad2 100644 --- a/pystac/extensions/raster.py +++ b/pystac/extensions/raster.py @@ -3,7 +3,6 @@ https://github.com/stac-extensions/raster """ -import enum from typing import Any, Dict, Iterable, List, Optional, Union import pystac @@ -12,19 +11,19 @@ PropertiesExtension, SummariesExtension, ) -from pystac.utils import get_opt, get_required, map_opt +from pystac.utils import StringEnum, get_opt, get_required, map_opt SCHEMA_URI = "https://stac-extensions.github.io/raster/v1.0.0/schema.json" BANDS_PROP = "raster:bands" -class Sampling(str, enum.Enum): +class Sampling(StringEnum): AREA = "area" POINT = "point" -class DataType(str, enum.Enum): +class DataType(StringEnum): INT8 = "int8" INT16 = "int16" INT32 = "int32" diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index cad6cb437..02ec3496a 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -3,7 +3,6 @@ https://github.com/stac-extensions/sar """ -import enum from typing import Any, Dict, Generic, Iterable, List, Optional, TypeVar, cast, Union import pystac @@ -15,7 +14,7 @@ SummariesExtension, ) from pystac.extensions.hooks import ExtensionHooks -from pystac.utils import get_required, map_opt +from pystac.utils import StringEnum, get_required, map_opt T = TypeVar("T", pystac.Item, pystac.Asset) @@ -40,7 +39,7 @@ OBSERVATION_DIRECTION_PROP: str = PREFIX + "observation_direction" -class FrequencyBand(str, enum.Enum): +class FrequencyBand(StringEnum): P = "P" L = "L" S = "S" @@ -51,14 +50,14 @@ class FrequencyBand(str, enum.Enum): KA = "Ka" -class Polarization(str, enum.Enum): +class Polarization(StringEnum): HH = "HH" VV = "VV" HV = "HV" VH = "VH" -class ObservationDirection(str, enum.Enum): +class ObservationDirection(StringEnum): LEFT = "left" RIGHT = "right" diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 21885e255..b89a9f562 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -3,7 +3,6 @@ https://github.com/stac-extensions/sat """ -import enum from datetime import datetime as Datetime from pystac.summaries import RangeSummary from typing import Dict, Any, List, Iterable, Generic, Optional, TypeVar, Union, cast @@ -15,7 +14,7 @@ SummariesExtension, ) from pystac.extensions.hooks import ExtensionHooks -from pystac.utils import str_to_datetime, datetime_to_str, map_opt +from pystac.utils import StringEnum, str_to_datetime, datetime_to_str, map_opt T = TypeVar("T", pystac.Item, pystac.Asset) @@ -31,7 +30,7 @@ ANX_DATETIME_PROP: str = PREFIX + "anx_datetime" -class OrbitState(str, enum.Enum): +class OrbitState(StringEnum): ASCENDING = "ascending" DESCENDING = "descending" GEOSTATIONARY = "geostationary" diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index 6cfcaddfa..1f3561fed 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -8,7 +8,6 @@ """ import copy -from enum import Enum from typing import Any, Dict, Generic, List, Optional, TypeVar, Union, cast from urllib import parse @@ -19,7 +18,7 @@ SummariesExtension, ) from pystac.extensions.hooks import ExtensionHooks -from pystac.utils import map_opt +from pystac.utils import StringEnum, map_opt T = TypeVar("T", pystac.Collection, pystac.Item) @@ -35,7 +34,7 @@ # Link rel type. -class ScientificRelType(str, Enum): +class ScientificRelType(StringEnum): """A list of rel types defined in the Scientific Citation Extension. See the :stac-ext:`Scientific Citation Extension Relation types diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index 17287ea3a..97478a50e 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -2,11 +2,11 @@ https://github.com/stac-extensions/version """ -from enum import Enum from pystac.utils import get_required, map_opt from typing import Generic, List, Optional, TypeVar, Union, cast import pystac +from pystac.utils import StringEnum from pystac.extensions.base import ( ExtensionManagementMixin, PropertiesExtension, @@ -23,7 +23,7 @@ DEPRECATED: str = "deprecated" -class VersionRelType(str, Enum): +class VersionRelType(StringEnum): """A list of rel types defined in the Version Extension. See the `Version Extension Relation types diff --git a/pystac/media_type.py b/pystac/media_type.py index d172c4511..57c47e256 100644 --- a/pystac/media_type.py +++ b/pystac/media_type.py @@ -1,7 +1,7 @@ -from enum import Enum +from pystac.utils import StringEnum -class MediaType(str, Enum): +class MediaType(StringEnum): """A list of common media types that can be used in STAC Asset and Link metadata.""" COG = "image/tiff; application=geotiff; profile=cloud-optimized" diff --git a/pystac/provider.py b/pystac/provider.py index 3e92823a1..d03d59c28 100644 --- a/pystac/provider.py +++ b/pystac/provider.py @@ -1,8 +1,9 @@ -from enum import Enum from typing import Any, Dict, List, Optional +from pystac.utils import StringEnum -class ProviderRole(str, Enum): + +class ProviderRole(StringEnum): """Enumerates the allows values of the Provider "role" field.""" LICENSOR = "licensor" diff --git a/pystac/rel_type.py b/pystac/rel_type.py index 234a45ead..61134a611 100644 --- a/pystac/rel_type.py +++ b/pystac/rel_type.py @@ -1,7 +1,7 @@ -from enum import Enum +from pystac.utils import StringEnum -class RelType(str, Enum): +class RelType(StringEnum): """A list of common rel types that can be used in STAC Link metadata. See :stac-spec:`"Using Relation Types ` in the STAC Best Practices for guidelines on using relation types. You may also want diff --git a/pystac/stac_object.py b/pystac/stac_object.py index e7aeb5104..f5b8234cd 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -1,17 +1,16 @@ from abc import ABC, abstractmethod -from enum import Enum from typing import Any, Dict, Iterable, List, Optional, Type, cast, TYPE_CHECKING, Union import pystac from pystac import STACError from pystac.link import Link -from pystac.utils import is_absolute_href, make_absolute_href +from pystac.utils import StringEnum, is_absolute_href, make_absolute_href if TYPE_CHECKING: from pystac.catalog import Catalog as Catalog_Type -class STACObjectType(str, Enum): +class STACObjectType(StringEnum): CATALOG = "Catalog" COLLECTION = "Collection" ITEM = "Feature" diff --git a/pystac/utils.py b/pystac/utils.py index 50cdbb1be..8232e7a0a 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -2,7 +2,7 @@ import posixpath from enum import Enum from pystac.errors import RequiredPropertyMissing -from typing import Any, Callable, Dict, List, Optional, TypeVar, Union +from typing import Any, Callable, Dict, List, Optional, TypeVar, Union, cast from urllib.parse import urljoin, urlparse, urlunparse, ParseResult as URLParseResult from datetime import datetime, timezone import dateutil.parser @@ -37,7 +37,14 @@ def safe_urlparse(href: str) -> URLParseResult: return parsed -class JoinType(str, Enum): +class StringEnum(str, Enum): + """Base Enum class for string enums that will serialize as the string value.""" + + def __str__(self) -> str: + return cast(str, self.value) + + +class JoinType(StringEnum): """Allowed join types for the :func:`_join` function.""" @staticmethod diff --git a/tests/test_link.py b/tests/test_link.py index 48ada4c54..070975abe 100644 --- a/tests/test_link.py +++ b/tests/test_link.py @@ -186,6 +186,17 @@ def test_title_as_init_argument(self) -> None: assert link.title == link_title assert link.to_dict().get("title") == link_title + def test_serialize_link(self) -> None: + href = "https://some-domain/path/to/item.json" + title = "A Test Link" + link = pystac.Link(pystac.RelType.SELF, href, pystac.MediaType.JSON, title) + link_dict = link.to_dict() + + self.assertEqual(str(link_dict["rel"]), "self") + self.assertEqual(str(link_dict["type"]), "application/json") + self.assertEqual(link_dict["title"], title) + self.assertEqual(link_dict["href"], href) + class StaticLinkTest(unittest.TestCase): def setUp(self) -> None: