diff --git a/docs/concepts.rst b/docs/concepts.rst index 498de29ab..257d0d7e8 100644 --- a/docs/concepts.rst +++ b/docs/concepts.rst @@ -475,44 +475,45 @@ Extension (e.g. Electro-Optical, Projection, etc.) and STAC Object (:class:`~pystac.Collection`, :class:`pystac.Item`, or :class:`pystac.Asset`). All classes that extend these objects inherit from :class:`pystac.extensions.base.PropertiesExtension`, and you can use the -``ext`` method on these classes to extend an object. +``ext`` accessor on the object to access the extension fields. For instance, if you have an item that implements the :stac-ext:`Electro-Optical -Extension `, you can access the properties associated with that extension using -:meth:`EOExtension.ext `: +Extension `, you can access the fields associated with that extension using +:meth:`Item.ext `: .. code-block:: python import pystac - from pystac.extensions.eo import EOExtension item = pystac.Item.from_file("tests/data-files/eo/eo-landsat-example.json") - # Check that the Item implements the EO Extension - if EOExtension.has_extension(item): - eo_ext = EOExtension.ext(item) + # As long as the Item implements the EO Extension you can access all the + # EO properties directly + bands = item.ext.eo.bands + cloud_cover = item.ext.eo.cloud_cover + ... - bands = eo_ext.bands - cloud_cover = eo_ext.cloud_cover - snow_cover = eo_ext.snow_cover - ... - -.. note:: The ``ext`` method will raise an :exc:`~pystac.ExtensionNotImplemented` +.. note:: ``ext`` will raise an :exc:`~pystac.ExtensionNotImplemented` exception if the object does not implement that extension (e.g. if the extension - URI is not in that object's :attr:`~pystac.STACObject.stac_extensions` list). In - the example above, we check that the Item implements the EO Extension before calling - :meth:`EOExtension.ext ` to handle this. See + URI is not in that object's :attr:`~pystac.STACObject.stac_extensions` list). See the `Adding an Extension`_ section below for details on adding an extension to an object. +If you don't want to raise an error you can use :meth:`~pystac.Item.ext.has` +to first check if the extension is implemented on your pystac object: + +.. code-block:: python + + if item.ext.has("eo"): + bands = item.ext.eo.bands + See the documentation for each extension implementation for details on the supported properties and other functionality. -Instances of :class:`~pystac.extensions.base.PropertiesExtension` have a -:attr:`~pystac.extensions.base.PropertiesExtension.properties` attribute that gives -access to the properties of the extended object. *This attribute is a reference to the -properties of the* :class:`~pystac.Item` *or* :class:`~pystac.Asset` *being extended and -can therefore mutate those properties.* For instance: +Extensions have access to the properties of the object. *This attribute is a reference +to the properties of the* :class:`~pystac.Collection`, :class:`~pystac.Item` *or* +:class:`~pystac.Asset` *being extended and can therefore mutate those properties.* +For instance: .. code-block:: python @@ -520,11 +521,10 @@ can therefore mutate those properties.* For instance: print(item.properties["eo:cloud_cover"]) # 78 - eo_ext = EOExtension.ext(item) - print(eo_ext.cloud_cover) + print(item.ext.eo.cloud_cover) # 78 - eo_ext.cloud_cover = 45 + item.ext.eo.cloud_cover = 45 print(item.properties["eo:cloud_cover"]) # 45 @@ -545,24 +545,17 @@ have a default value of ``None``: .. code-block:: python # Can also omit cloud_cover entirely... - eo_ext.apply(0.5, bands, cloud_cover=None) - - -If you attempt to extend an object that is not supported by an extension, PySTAC will -throw a :class:`pystac.ExtensionTypeError`. + item.ext.eo.apply(0.5, bands, cloud_cover=None) Adding an Extension ------------------- You can add an extension to a STAC object that does not already implement that extension -using the :meth:`ExtensionManagementMixin.add_to -` method. Any concrete -extension implementations that extend existing STAC objects should inherit from the -:class:`~pystac.extensions.base.ExtensionManagementMixin` class, and will therefore have -this method available. The -:meth:`~pystac.extensions.base.ExtensionManagementMixin.add_to` adds the correct schema -URI to the :attr:`~pystac.STACObject.stac_extensions` list for the object being +using the :meth:`~pystac.Item.ext.add` method. Any concrete +extension implementations that extend existing STAC objects should have +this method available. The :meth:`~pystac.Item.ext.add` method adds the correct schema +URI to the :attr:`~pystac.Item.stac_extensions` list for the object being extended. .. code-block:: python @@ -573,7 +566,7 @@ extended. # [] # Add the Electro-Optical extension - EOExtension.add_to(item) + item.ext.add("eo") print(item.stac_extensions) # ['https://stac-extensions.github.io/eo/v1.1.0/schema.json'] @@ -617,7 +610,7 @@ Item Asset properties ===================== Properties that apply to Items can be found in two places: the Item's properties or in -any of an Item's Assets. If the property is on an Asset, it applies only that specific +any of an Item's Assets. If the property is on an Asset, it applies only to that specific asset. For example, gsd defined for an Item represents the best Ground Sample Distance (resolution) for the data within the Item. However, some assets may be lower resolution and thus have a higher gsd. In that case, the `gsd` can be found on the Asset. diff --git a/docs/quickstart.ipynb b/docs/quickstart.ipynb index 29898bfa0..5645f4b83 100644 --- a/docs/quickstart.ipynb +++ b/docs/quickstart.ipynb @@ -36,9 +36,7 @@ "import tempfile\n", "from pathlib import Path\n", "\n", - "from pystac import Catalog, get_stac_version\n", - "from pystac.extensions.eo import EOExtension\n", - "from pystac.extensions.label import LabelExtension" + "from pystac import Catalog, get_stac_version" ] }, { @@ -340,11 +338,9 @@ " font-weight: 700;\n", "}\n", ".pystac-key-value {\n", - " display: inline-block;\n", - " margin: 0px 0.5em 0px 0px;\n", + " display: inline-block;\n", + " margin: 0px 0.5em 0px 0px;\n", "}\n", - "\n", - "\n", "\n", "
\n", "
\n", @@ -398,9 +394,7 @@ " \n", " \n", "
  • \n", - " \n", - " 0\n", - " \n", + " 0\n", "
      \n", " \n", " \n", @@ -416,7 +410,25 @@ " \n", "
    • \n", " href\n", - " \"../catalog.json\"\n", + " \"/home/jsignell/pystac/docs/example-catalog/catalog.json\"\n", + "
    • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    • \n", + " type\n", + " \"application/json\"\n", + "
    • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    • \n", + " title\n", + " \"STAC for Landsat data\"\n", "
    • \n", " \n", " \n", @@ -433,16 +445,14 @@ " \n", " \n", "
    • \n", - " \n", - " 1\n", - " \n", + " 1\n", "
        \n", " \n", " \n", " \n", "
      • \n", " rel\n", - " \"parent\"\n", + " \"item\"\n", "
      • \n", " \n", " \n", @@ -451,7 +461,7 @@ " \n", "
      • \n", " href\n", - " \"../catalog.json\"\n", + " \"/home/jsignell/pystac/docs/example-catalog/landsat-8-l1/2018-06/LC80140332018166LGN00.json\"\n", "
      • \n", " \n", " \n", @@ -468,9 +478,7 @@ " \n", " \n", "
      • \n", - " \n", - " 2\n", - " \n", + " 2\n", "
          \n", " \n", " \n", @@ -486,7 +494,7 @@ " \n", "
        • \n", " href\n", - " \"./2018-06/LC80140332018166LGN00.json\"\n", + " \"/home/jsignell/pystac/docs/example-catalog/landsat-8-l1/2018-05/LC80150322018141LGN00.json\"\n", "
        • \n", " \n", " \n", @@ -503,9 +511,7 @@ " \n", " \n", "
        • \n", - " \n", - " 3\n", - " \n", + " 3\n", "
            \n", " \n", " \n", @@ -521,7 +527,7 @@ " \n", "
          • \n", " href\n", - " \"./2018-05/LC80150322018141LGN00.json\"\n", + " \"/home/jsignell/pystac/docs/example-catalog/landsat-8-l1/2018-07/LC80150332018189LGN00.json\"\n", "
          • \n", " \n", " \n", @@ -538,9 +544,7 @@ " \n", " \n", "
          • \n", - " \n", - " 4\n", - " \n", + " 4\n", "
              \n", " \n", " \n", @@ -556,7 +560,7 @@ " \n", "
            • \n", " href\n", - " \"./2018-07/LC80150332018189LGN00.json\"\n", + " \"/home/jsignell/pystac/docs/example-catalog/landsat-8-l1/2018-06/LC80300332018166LGN00.json\"\n", "
            • \n", " \n", " \n", @@ -573,16 +577,14 @@ " \n", " \n", "
            • \n", - " \n", - " 5\n", - " \n", + " 5\n", "
                \n", " \n", " \n", " \n", "
              • \n", " rel\n", - " \"item\"\n", + " \"self\"\n", "
              • \n", " \n", " \n", @@ -591,7 +593,16 @@ " \n", "
              • \n", " href\n", - " \"./2018-06/LC80300332018166LGN00.json\"\n", + " \"/home/jsignell/pystac/docs/example-catalog/landsat-8-l1/collection.json\"\n", + "
              • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
              • \n", + " type\n", + " \"application/json\"\n", "
              • \n", " \n", " \n", @@ -608,16 +619,14 @@ " \n", " \n", "
              • \n", - " \n", - " 6\n", - " \n", + " 6\n", "
                  \n", " \n", " \n", " \n", "
                • \n", " rel\n", - " \"self\"\n", + " \"parent\"\n", "
                • \n", " \n", " \n", @@ -626,7 +635,7 @@ " \n", "
                • \n", " href\n", - " \"/home/jsignell/pystac/docs/example-catalog/landsat-8-l1/collection.json\"\n", + " \"/home/jsignell/pystac/docs/example-catalog/catalog.json\"\n", "
                • \n", " \n", " \n", @@ -640,6 +649,15 @@ " \n", " \n", " \n", + " \n", + " \n", + "
                • \n", + " title\n", + " \"STAC for Landsat data\"\n", + "
                • \n", + " \n", + " \n", + " \n", "
                \n", "
              • \n", " \n", @@ -676,9 +694,7 @@ " \n", " \n", "
              • \n", - " \n", - " properties\n", - " \n", + " properties\n", "
                  \n", " \n", " \n", @@ -734,9 +750,7 @@ " \n", " \n", "
                • \n", - " \n", - " 0\n", - " \n", + " 0\n", "
                    \n", " \n", " \n", @@ -787,9 +801,7 @@ " \n", " \n", "
                  • \n", - " \n", - " 1\n", - " \n", + " 1\n", "
                      \n", " \n", " \n", @@ -840,9 +852,7 @@ " \n", " \n", "
                    • \n", - " \n", - " 2\n", - " \n", + " 2\n", "
                        \n", " \n", " \n", @@ -893,9 +903,7 @@ " \n", " \n", "
                      • \n", - " \n", - " 3\n", - " \n", + " 3\n", "
                          \n", " \n", " \n", @@ -946,9 +954,7 @@ " \n", " \n", "
                        • \n", - " \n", - " 4\n", - " \n", + " 4\n", "
                            \n", " \n", " \n", @@ -999,9 +1005,7 @@ " \n", " \n", "
                          • \n", - " \n", - " 5\n", - " \n", + " 5\n", "
                              \n", " \n", " \n", @@ -1052,9 +1056,7 @@ " \n", " \n", "
                            • \n", - " \n", - " 6\n", - " \n", + " 6\n", "
                                \n", " \n", " \n", @@ -1105,9 +1107,7 @@ " \n", " \n", "
                              • \n", - " \n", - " 7\n", - " \n", + " 7\n", "
                                  \n", " \n", " \n", @@ -1158,9 +1158,7 @@ " \n", " \n", "
                                • \n", - " \n", - " 8\n", - " \n", + " 8\n", "
                                    \n", " \n", " \n", @@ -1211,9 +1209,7 @@ " \n", " \n", "
                                  • \n", - " \n", - " 9\n", - " \n", + " 9\n", "
                                      \n", " \n", " \n", @@ -1264,9 +1260,7 @@ " \n", " \n", "
                                    • \n", - " \n", - " 10\n", - " \n", + " 10\n", "
                                        \n", " \n", " \n", @@ -1377,17 +1371,13 @@ " \n", " \n", "
                                      • \n", - " \n", - " extent\n", - " \n", + " extent\n", "
                                          \n", " \n", " \n", " \n", "
                                        • \n", - " \n", - " spatial\n", - " \n", + " spatial\n", "
                                            \n", " \n", " \n", @@ -1474,9 +1464,7 @@ " \n", " \n", "
                                          • \n", - " \n", - " temporal\n", - " \n", + " temporal\n", "
                                              \n", " \n", " \n", @@ -1609,9 +1597,7 @@ " \n", " \n", "
                                            • \n", - " \n", - " 0\n", - " \n", + " 0\n", "
                                                \n", " \n", " \n", @@ -1789,7 +1775,7 @@ "source": [ "This Item implements the [Electro-Optical](https://github.com/stac-extensions/eo), [View Geometry](https://github.com/stac-extensions/view), and [Projection](https://github.com/stac-extensions/projection) Extensions. \n", "\n", - "We can also check if a specific extension is implemented using the [has_extension](https://pystac.readthedocs.io/en/latest/api.html#pystac.extensions.base.ExtensionManagementMixin.has_extension) method for that extension class." + "We can also check if a specific extension is implemented using [ext.has](https://pystac.readthedocs.io/en/latest/api.html#pystac.item.ext.has) with the name of that extension." ] }, { @@ -1809,7 +1795,7 @@ } ], "source": [ - "EOExtension.has_extension(item)" + "item.ext.has(\"eo\")" ] }, { @@ -1829,14 +1815,14 @@ } ], "source": [ - "LabelExtension.has_extension(item)" + "item.ext.has(\"raster\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We can access fields associated with the extension as attributes on the extension instance. For instance, the [\"eo:cloud_cover\" field](https://github.com/stac-extensions/eo#item-properties-or-asset-fields) defined in the Electro-Optical Extension can be accessed using the [EOExtension.cloud_cover](https://pystac.readthedocs.io/en/latest/api.html#pystac.extensions.eo.EOExtension.cloud_cover) attribute." + "We can access fields associated with the extension as attributes on the extension instance. For instance, the [\"eo:cloud_cover\" field](https://github.com/stac-extensions/eo#item-properties-or-asset-fields) defined in the Electro-Optical Extension can be accessed using the [item.ext.eo.cloud_cover](https://pystac.readthedocs.io/en/latest/api.html#pystac.extensions.eo.EOExtension.cloud_cover) attribute." ] }, { @@ -1856,8 +1842,7 @@ } ], "source": [ - "eo_item_ext = EOExtension.ext(item)\n", - "eo_item_ext.cloud_cover" + "item.ext.eo.cloud_cover" ] }, { @@ -1987,8 +1972,7 @@ } ], "source": [ - "eo_asset_ext = EOExtension.ext(asset)\n", - "bands = eo_asset_ext.bands\n", + "bands = asset.ext.eo.bands\n", "bands" ] }, @@ -2042,10 +2026,9 @@ "outputs": [], "source": [ "item_to_update = next(root_catalog.get_items(\"LC80140332018166LGN00\", recursive=True))\n", - "item_to_update_eo_ext = EOExtension.ext(item_to_update)\n", "\n", "# Update the cloud cover\n", - "item_to_update_eo_ext.cloud_cover = 30\n", + "item_to_update.ext.eo.cloud_cover = 30\n", "\n", "# Add the instrument field\n", "item_to_update.common_metadata.instruments = [\"LANDSAT\"]" @@ -2103,7 +2086,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Catalog saved to: /tmp/tmp_nahzekl/catalog.json\n" + "Catalog saved to: /tmp/tmp9bmp70k9/catalog.json\n" ] } ], @@ -2162,7 +2145,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.15" + "version": "3.11.6" }, "vscode": { "interpreter": { diff --git a/docs/tutorials/adding-new-and-custom-extensions.ipynb b/docs/tutorials/adding-new-and-custom-extensions.ipynb index 78e2f9a8c..15c3e9413 100644 --- a/docs/tutorials/adding-new-and-custom-extensions.ipynb +++ b/docs/tutorials/adding-new-and-custom-extensions.ipynb @@ -6,7 +6,7 @@ "source": [ "## Adding New and Custom Extensions\n", "\n", - "This tutorial will cover using the `PropertiesExtension` and `ExtensionManagementMixin` classes in `pystac.extensions.base` to implement a new extension to PySTAC.\n", + "This tutorial will cover using the `PropertiesExtension` and `ExtensionManagementMixin` classes in `pystac.extensions.base` to implement a new extension in PySTAC, and how to make that class accessible via the `pystac.Item.ext` interface.\n", "\n", "For this exercise, we will implement an imaginary Order Request Extension that allows us to track an internal order ID associated with a given satellite image, as well as the history of that imagery order. This use-case is specific enough that it would probably not be a good candidate for an actual STAC Extension, but it gives us an opportunity to highlight some of the key aspects and patterns used in implementing STAC Extensions in PySTAC." ] @@ -24,6 +24,7 @@ "metadata": {}, "outputs": [], "source": [ + "from typing import Literal\n", "from datetime import datetime, timedelta\n", "from pprint import pprint\n", "from typing import Any, Dict, List, Optional, Union\n", @@ -162,6 +163,8 @@ "class OrderExtension(\n", " PropertiesExtension, ExtensionManagementMixin[Union[pystac.Item, pystac.Collection]]\n", "):\n", + " name: Literal[\"order\"] = \"order\"\n", + "\n", " def __init__(self, item: pystac.Item):\n", " self.item = item\n", " self.properties = item.properties\n", @@ -339,11 +342,11 @@ "output_type": "stream", "text": [ "Implements Extension: True\n", - "Order ID: f6f367b6-a787-48de-941f-08a04be77683\n", + "Order ID: 7a206229-78f0-46cb-afc2-acf45e14afab\n", "History:\n", - "{'timestamp': '2022-01-20T11:44:01.803820Z', 'type': 'submitted'}\n", - "{'timestamp': '2022-01-20T23:44:01.803939Z', 'type': 'started_processing'}\n", - "{'timestamp': '2022-01-21T10:44:01.803999Z', 'type': 'delivered'}\n" + "{'timestamp': '2023-10-11T11:21:50.989315Z', 'type': 'submitted'}\n", + "{'timestamp': '2023-10-11T23:21:50.989372Z', 'type': 'started_processing'}\n", + "{'timestamp': '2023-10-12T10:21:50.989403Z', 'type': 'delivered'}\n" ] } ], @@ -356,11 +359,66 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "## (Optional) Add access via `Item.ext`\n", + "\n", + "_This applies if you are planning on opening a Pull Request to add this implementation of the extension class to the pystac library_\n", + "\n", + "Now that you have a complete extension class, you can add access to it via the `pystac.Item.ext` interface by following these steps:\n", + "\n", + "1) Make sure that your Extension class has a `name` attribute with `Literal()` as the type.\n", + "2) Import your Extension class in `pystac/extensions/ext.py`\n", + "3) Add the `name` to `EXTENSION_NAMES`\n", + "4) Add the mapping from name to class to `EXTENSION_NAME_MAPPING`\n", + "5) Add a getter method to the Ext class for any object type that this extension works with.\n", + "\n", + "Here is an example of the diff:\n", + "\n", + "```diff\n", + "diff --git a/pystac/extensions/ext.py b/pystac/extensions/ext.py\n", + "index 93a30fe..2dbe5ca 100644\n", + "--- a/pystac/extensions/ext.py\n", + "+++ b/pystac/extensions/ext.py\n", + "@@ -9,6 +9,7 @@ from pystac.extensions.file import FileExtension\n", + " from pystac.extensions.grid import GridExtension\n", + " from pystac.extensions.item_assets import AssetDefinition, ItemAssetsExtension\n", + " from pystac.extensions.mgrs import MgrsExtension\n", + "+from pystac.extensions.order import OrderExtension\n", + " from pystac.extensions.pointcloud import PointcloudExtension\n", + " from pystac.extensions.projection import ProjectionExtension\n", + " from pystac.extensions.raster import RasterExtension\n", + "@@ -32,6 +33,7 @@ EXTENSION_NAMES = Literal[\n", + " \"grid\",\n", + " \"item_assets\",\n", + " \"mgrs\",\n", + "+ \"order\",\n", + " \"pc\",\n", + " \"proj\",\n", + " \"raster\",\n", + "@@ -54,6 +56,7 @@ EXTENSION_NAME_MAPPING: Dict[EXTENSION_NAMES, Any] = {\n", + " GridExtension.name: GridExtension,\n", + " ItemAssetsExtension.name: ItemAssetsExtension,\n", + " MgrsExtension.name: MgrsExtension,\n", + "+ OrderExtension.name: OrderExtension,\n", + " PointcloudExtension.name: PointcloudExtension,\n", + " ProjectionExtension.name: ProjectionExtension,\n", + " RasterExtension.name: RasterExtension,\n", + "@@ -150,6 +153,10 @@ class ItemExt:\n", + " def mgrs(self) -> MgrsExtension:\n", + " return MgrsExtension.ext(self.stac_object)\n", + " \n", + "+ @property\n", + "+ def order(self) -> OrderExtension:\n", + "+ return OrderExtension.ext(self.stac_object)\n", + "+\n", + " @property\n", + " def pc(self) -> PointcloudExtension[pystac.Item]:\n", + " return PointcloudExtension.ext(self.stac_object)\n", + "```\n", + "\n" + ] } ], "metadata": { @@ -379,7 +437,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.9" + "version": "3.11.6" } }, "nbformat": 4, diff --git a/docs/tutorials/how-to-create-stac-catalogs.ipynb b/docs/tutorials/how-to-create-stac-catalogs.ipynb index 757a7840f..a9efbb5e8 100644 --- a/docs/tutorials/how-to-create-stac-catalogs.ipynb +++ b/docs/tutorials/how-to-create-stac-catalogs.ipynb @@ -91,7 +91,7 @@ { "data": { "text/plain": [ - "('/tmp/tmpbxjclo_s/image.tif', )" + "('/tmp/tmpdsdpun_y/image.tif', )" ] }, "execution_count": 4, @@ -121,43 +121,41 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "\u001b[0;31mInit signature:\u001b[0m\n", - "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCatalog\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mid\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdescription\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtitle\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstac_extensions\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mcatalog_type\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'CatalogType'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m<\u001b[0m\u001b[0mCatalogType\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mABSOLUTE_PUBLISHED\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'ABSOLUTE_PUBLISHED'\u001b[0m\u001b[0;34m>\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m \n", - "A PySTAC Catalog represents a STAC catalog in memory.\n", - "\n", - "A Catalog is a :class:`~pystac.STACObject` that may contain children,\n", - "which are instances of :class:`~pystac.Catalog` or :class:`~pystac.Collection`,\n", - "as well as :class:`~pystac.Item` s.\n", - "\n", - "Args:\n", - " id : Identifier for the catalog. Must be unique within the STAC.\n", - " description : Detailed multi-line description to fully explain the catalog.\n", - " `CommonMark 0.29 syntax `_ MAY be used for rich\n", - " text representation.\n", - " title : Optional short descriptive one-line title for the catalog.\n", - " stac_extensions : Optional list of extensions the Catalog implements.\n", - " href : Optional HREF for this catalog, which be set as the\n", - " catalog's self link's HREF.\n", - " catalog_type : Optional catalog type for this catalog. Must\n", - " be one of the values in :class:`~pystac.CatalogType`.\n", - "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/catalog.py\n", - "\u001b[0;31mType:\u001b[0m ABCMeta\n", - "\u001b[0;31mSubclasses:\u001b[0m Collection" - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mInit signature:\u001b[0m\n", + "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCatalog\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mid\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mdescription\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtitle\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mstac_extensions\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcatalog_type\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'CatalogType'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mABSOLUTE_PUBLISHED\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mDocstring:\u001b[0m \n", + "A PySTAC Catalog represents a STAC catalog in memory.\n", + "\n", + "A Catalog is a :class:`~pystac.STACObject` that may contain children,\n", + "which are instances of :class:`~pystac.Catalog` or :class:`~pystac.Collection`,\n", + "as well as :class:`~pystac.Item` s.\n", + "\n", + "Args:\n", + " id : Identifier for the catalog. Must be unique within the STAC.\n", + " description : Detailed multi-line description to fully explain the catalog.\n", + " `CommonMark 0.29 syntax `_ MAY be used for rich\n", + " text representation.\n", + " title : Optional short descriptive one-line title for the catalog.\n", + " stac_extensions : Optional list of extensions the Catalog implements.\n", + " href : Optional HREF for this catalog, which be set as the\n", + " catalog's self link's HREF.\n", + " catalog_type : Optional catalog type for this catalog. Must\n", + " be one of the values in :class:`~pystac.CatalogType`.\n", + "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/catalog.py\n", + "\u001b[0;31mType:\u001b[0m ABCMeta\n", + "\u001b[0;31mSubclasses:\u001b[0m Collection" + ] } ], "source": [ @@ -219,61 +217,59 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "\u001b[0;31mInit signature:\u001b[0m\n", - "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mItem\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mid\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mgeometry\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mbbox\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[float]]'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdatetime\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Datetime]'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mproperties\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Dict[str, Any]'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstart_datetime\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Datetime]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mend_datetime\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Datetime]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstac_extensions\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mcollection\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Union[str, Collection]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0massets\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Asset]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m \n", - "An Item is the core granular entity in a STAC, containing the core metadata\n", - "that enables any client to search or crawl online catalogs of spatial 'assets' -\n", - "satellite imagery, derived data, DEM's, etc.\n", - "\n", - "Args:\n", - " id : Provider identifier. Must be unique within the STAC.\n", - " geometry : Defines the full footprint of the asset represented by this\n", - " item, formatted according to\n", - " `RFC 7946, section 3.1 (GeoJSON) `_.\n", - " bbox : Bounding Box of the asset represented by this item\n", - " using either 2D or 3D geometries. The length of the array must be 2*n\n", - " where n is the number of dimensions. Could also be None in the case of a\n", - " null geometry.\n", - " datetime : datetime associated with this item. If None,\n", - " a start_datetime and end_datetime must be supplied.\n", - " properties : A dictionary of additional metadata for the item.\n", - " start_datetime : Optional start datetime, part of common metadata. This value\n", - " will override any `start_datetime` key in properties.\n", - " end_datetime : Optional end datetime, part of common metadata. This value\n", - " will override any `end_datetime` key in properties.\n", - " stac_extensions : Optional list of extensions the Item implements.\n", - " href : Optional HREF for this item, which be set as the item's\n", - " self link's HREF.\n", - " collection : The Collection or Collection ID that this item\n", - " belongs to.\n", - " extra_fields : Extra fields that are part of the top-level JSON\n", - " properties of the Item.\n", - " assets : A dictionary mapping string keys to :class:`~pystac.Asset` objects. All\n", - " :class:`~pystac.Asset` values in the dictionary will have their\n", - " :attr:`~pystac.Asset.owner` attribute set to the created Item.\n", - "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/item.py\n", - "\u001b[0;31mType:\u001b[0m ABCMeta\n", - "\u001b[0;31mSubclasses:\u001b[0m " - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mInit signature:\u001b[0m\n", + "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mItem\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mid\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mgeometry\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mbbox\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[float]]'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mdatetime\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Datetime]'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mproperties\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Dict[str, Any]'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mstart_datetime\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Datetime]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mend_datetime\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Datetime]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mstac_extensions\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcollection\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Union[str, Collection]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0massets\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Asset]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mDocstring:\u001b[0m \n", + "An Item is the core granular entity in a STAC, containing the core metadata\n", + "that enables any client to search or crawl online catalogs of spatial 'assets' -\n", + "satellite imagery, derived data, DEM's, etc.\n", + "\n", + "Args:\n", + " id : Provider identifier. Must be unique within the STAC.\n", + " geometry : Defines the full footprint of the asset represented by this\n", + " item, formatted according to\n", + " `RFC 7946, section 3.1 (GeoJSON) `_.\n", + " bbox : Bounding Box of the asset represented by this item\n", + " using either 2D or 3D geometries. The length of the array must be 2*n\n", + " where n is the number of dimensions. Could also be None in the case of a\n", + " null geometry.\n", + " datetime : datetime associated with this item. If None,\n", + " a start_datetime and end_datetime must be supplied.\n", + " properties : A dictionary of additional metadata for the item.\n", + " start_datetime : Optional start datetime, part of common metadata. This value\n", + " will override any `start_datetime` key in properties.\n", + " end_datetime : Optional end datetime, part of common metadata. This value\n", + " will override any `end_datetime` key in properties.\n", + " stac_extensions : Optional list of extensions the Item implements.\n", + " href : Optional HREF for this item, which be set as the item's\n", + " self link's HREF.\n", + " collection : The Collection or Collection ID that this item\n", + " belongs to.\n", + " extra_fields : Extra fields that are part of the top-level JSON\n", + " properties of the Item.\n", + " assets : A dictionary mapping string keys to :class:`~pystac.Asset` objects. All\n", + " :class:`~pystac.Asset` values in the dictionary will have their\n", + " :attr:`~pystac.Asset.owner` attribute set to the created Item.\n", + "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/item.py\n", + "\u001b[0;31mType:\u001b[0m ABCMeta\n", + "\u001b[0;31mSubclasses:\u001b[0m " + ] } ], "source": [ @@ -377,7 +373,70 @@ "cell_type": "code", "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "
                                                \n", + "
                                                \n", + "
                                                  \n", + " \n", + " \n", + " \n", + "
                                                • \n", + " rel\n", + " \"item\"\n", + "
                                                • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                • \n", + " href\n", + " None\n", + "
                                                • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                • \n", + " type\n", + " \"application/json\"\n", + "
                                                • \n", + " \n", + " \n", + " \n", + "
                                                \n", + "
                                                \n", + "
                                                " + ], + "text/plain": [ + ">" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "catalog.add_item(item)" ] @@ -402,11 +461,9 @@ " font-weight: 700;\n", "}\n", ".pystac-key-value {\n", - " display: inline-block;\n", - " margin: 0px 0.5em 0px 0px;\n", + " display: inline-block;\n", + " margin: 0px 0.5em 0px 0px;\n", "}\n", - "\n", - "\n", "\n", "
                                                \n", "
                                                \n", @@ -460,9 +517,7 @@ " \n", " \n", "
                                              • \n", - " \n", - " 0\n", - " \n", + " 0\n", "
                                                  \n", " \n", " \n", @@ -559,42 +614,40 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "\u001b[0;31mInit signature:\u001b[0m\n", - "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAsset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtitle\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdescription\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mmedia_type\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mroles\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m'None'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m \n", - "An object that contains a link to data associated with an Item or Collection that\n", - "can be downloaded or streamed.\n", - "\n", - "Args:\n", - " href : Link to the asset object. Relative and absolute links are both\n", - " allowed.\n", - " title : Optional displayed title for clients and users.\n", - " description : A description of the Asset providing additional details,\n", - " such as how it was processed or created. CommonMark 0.29 syntax MAY be used\n", - " for rich text representation.\n", - " media_type : Optional description of the media type. Registered Media Types\n", - " are preferred. See :class:`~pystac.MediaType` for common media types.\n", - " roles : Optional, Semantic roles (i.e. thumbnail, overview,\n", - " data, metadata) of the asset.\n", - " extra_fields : Optional, additional fields for this asset. This is used\n", - " by extensions as a way to serialize and deserialize properties on asset\n", - " object JSON.\n", - "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/asset.py\n", - "\u001b[0;31mType:\u001b[0m type\n", - "\u001b[0;31mSubclasses:\u001b[0m " - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mInit signature:\u001b[0m\n", + "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAsset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtitle\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mdescription\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mmedia_type\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mroles\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m'None'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mDocstring:\u001b[0m \n", + "An object that contains a link to data associated with an Item or Collection that\n", + "can be downloaded or streamed.\n", + "\n", + "Args:\n", + " href : Link to the asset object. Relative and absolute links are both\n", + " allowed.\n", + " title : Optional displayed title for clients and users.\n", + " description : A description of the Asset providing additional details,\n", + " such as how it was processed or created. CommonMark 0.29 syntax MAY be used\n", + " for rich text representation.\n", + " media_type : Optional description of the media type. Registered Media Types\n", + " are preferred. See :class:`~pystac.MediaType` for common media types.\n", + " roles : Optional, Semantic roles (i.e. thumbnail, overview,\n", + " data, metadata) of the asset.\n", + " extra_fields : Optional, additional fields for this asset. This is used\n", + " by extensions as a way to serialize and deserialize properties on asset\n", + " object JSON.\n", + "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/asset.py\n", + "\u001b[0;31mType:\u001b[0m type\n", + "\u001b[0;31mSubclasses:\u001b[0m " + ] } ], "source": [ @@ -633,7 +686,7 @@ " \"stac_version\": \"1.0.0\",\n", " \"id\": \"local-image\",\n", " \"properties\": {\n", - " \"datetime\": \"2023-06-06T16:00:38.116601Z\"\n", + " \"datetime\": \"2023-10-12T15:35:17.290343Z\"\n", " },\n", " \"geometry\": {\n", " \"type\": \"Polygon\",\n", @@ -676,7 +729,7 @@ " ],\n", " \"assets\": {\n", " \"image\": {\n", - " \"href\": \"/tmp/tmpbxjclo_s/image.tif\",\n", + " \"href\": \"/tmp/tmpdsdpun_y/image.tif\",\n", " \"type\": \"image/tiff; application=geotiff\"\n", " }\n", " },\n", @@ -769,8 +822,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "/tmp/tmpbxjclo_s/stac/catalog.json\n", - "/tmp/tmpbxjclo_s/stac/local-image/local-image.json\n" + "/tmp/tmpdsdpun_y/stac/catalog.json\n", + "/tmp/tmpdsdpun_y/stac/local-image/local-image.json\n" ] } ], @@ -806,9 +859,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "/tmp/tmpbxjclo_s/stac/catalog.json\n", + "/tmp/tmpdsdpun_y/stac/catalog.json\n", "\n", - "/tmp/tmpbxjclo_s/stac/local-image:\n", + "/tmp/tmpdsdpun_y/stac/local-image:\n", "local-image.json\n" ] } @@ -866,7 +919,7 @@ " \"stac_version\": \"1.0.0\",\n", " \"id\": \"local-image\",\n", " \"properties\": {\n", - " \"datetime\": \"2023-06-06T16:00:38.116601Z\"\n", + " \"datetime\": \"2023-10-12T15:35:17.290343Z\"\n", " },\n", " \"geometry\": {\n", " \"type\": \"Polygon\",\n", @@ -909,7 +962,7 @@ " ],\n", " \"assets\": {\n", " \"image\": {\n", - " \"href\": \"/tmp/tmpbxjclo_s/image.tif\",\n", + " \"href\": \"/tmp/tmpdsdpun_y/image.tif\",\n", " \"type\": \"image/tiff; application=geotiff\"\n", " }\n", " },\n", @@ -966,7 +1019,7 @@ " \"stac_version\": \"1.0.0\",\n", " \"id\": \"local-image\",\n", " \"properties\": {\n", - " \"datetime\": \"2023-06-06T16:00:38.116601Z\"\n", + " \"datetime\": \"2023-10-12T15:35:17.290343Z\"\n", " },\n", " \"geometry\": {\n", " \"type\": \"Polygon\",\n", @@ -998,23 +1051,23 @@ " \"links\": [\n", " {\n", " \"rel\": \"root\",\n", - " \"href\": \"/tmp/tmpbxjclo_s/stac/catalog.json\",\n", + " \"href\": \"/tmp/tmpdsdpun_y/stac/catalog.json\",\n", " \"type\": \"application/json\"\n", " },\n", " {\n", " \"rel\": \"parent\",\n", - " \"href\": \"/tmp/tmpbxjclo_s/stac/catalog.json\",\n", + " \"href\": \"/tmp/tmpdsdpun_y/stac/catalog.json\",\n", " \"type\": \"application/json\"\n", " },\n", " {\n", " \"rel\": \"self\",\n", - " \"href\": \"/tmp/tmpbxjclo_s/stac/local-image/local-image.json\",\n", + " \"href\": \"/tmp/tmpdsdpun_y/stac/local-image/local-image.json\",\n", " \"type\": \"application/json\"\n", " }\n", " ],\n", " \"assets\": {\n", " \"image\": {\n", - " \"href\": \"/tmp/tmpbxjclo_s/image.tif\",\n", + " \"href\": \"/tmp/tmpdsdpun_y/image.tif\",\n", " \"type\": \"image/tiff; application=geotiff\"\n", " }\n", " },\n", @@ -1065,7 +1118,7 @@ " \"stac_version\": \"1.0.0\",\n", " \"id\": \"local-image\",\n", " \"properties\": {\n", - " \"datetime\": \"2023-06-06T16:00:38.116601Z\"\n", + " \"datetime\": \"2023-10-12T15:35:17.290343Z\"\n", " },\n", " \"geometry\": {\n", " \"type\": \"Polygon\",\n", @@ -1150,7 +1203,7 @@ "metadata": {}, "outputs": [], "source": [ - "from pystac.extensions.eo import Band, EOExtension\n", + "from pystac.extensions.eo import Band\n", "\n", "# From: https://www.spaceimagingme.com/downloads/sensors/datasheets/DG_WorldView3_DS_2014.pdf\n", "\n", @@ -1203,8 +1256,8 @@ " datetime=datetime.utcnow(),\n", " properties={},\n", ")\n", - "eo = EOExtension.ext(eo_item, add_if_missing=True)\n", - "eo.apply(bands=wv3_bands)" + "eo_item.ext.add(\"eo\")\n", + "eo_item.ext.eo.bands = wv3_bands" ] }, { @@ -1245,11 +1298,9 @@ " font-weight: 700;\n", "}\n", ".pystac-key-value {\n", - " display: inline-block;\n", - " margin: 0px 0.5em 0px 0px;\n", + " display: inline-block;\n", + " margin: 0px 0.5em 0px 0px;\n", "}\n", - "\n", - "\n", "\n", "
                                                  \n", "
                                                  \n", @@ -1285,9 +1336,7 @@ " \n", " \n", "
                                                • \n", - " \n", - " properties\n", - " \n", + " properties\n", "
                                                    \n", " \n", " \n", @@ -1302,9 +1351,7 @@ " \n", " \n", "
                                                  • \n", - " \n", - " 0\n", - " \n", + " 0\n", "
                                                      \n", " \n", " \n", @@ -1346,9 +1393,7 @@ " \n", " \n", "
                                                    • \n", - " \n", - " 1\n", - " \n", + " 1\n", "
                                                        \n", " \n", " \n", @@ -1390,9 +1435,7 @@ " \n", " \n", "
                                                      • \n", - " \n", - " 2\n", - " \n", + " 2\n", "
                                                          \n", " \n", " \n", @@ -1434,9 +1477,7 @@ " \n", " \n", "
                                                        • \n", - " \n", - " 3\n", - " \n", + " 3\n", "
                                                            \n", " \n", " \n", @@ -1478,9 +1519,7 @@ " \n", " \n", "
                                                          • \n", - " \n", - " 4\n", - " \n", + " 4\n", "
                                                              \n", " \n", " \n", @@ -1522,9 +1561,7 @@ " \n", " \n", "
                                                            • \n", - " \n", - " 5\n", - " \n", + " 5\n", "
                                                                \n", " \n", " \n", @@ -1566,9 +1603,7 @@ " \n", " \n", "
                                                              • \n", - " \n", - " 6\n", - " \n", + " 6\n", "
                                                                  \n", " \n", " \n", @@ -1610,9 +1645,7 @@ " \n", " \n", "
                                                                • \n", - " \n", - " 7\n", - " \n", + " 7\n", "
                                                                    \n", " \n", " \n", @@ -1697,7 +1730,7 @@ " \n", "
                                                                  • \n", " datetime\n", - " \"2023-06-06T16:00:38.592649Z\"\n", + " \"2023-10-12T15:35:17.781985Z\"\n", "
                                                                  • \n", " \n", " \n", @@ -1710,9 +1743,7 @@ " \n", " \n", "
                                                                  • \n", - " \n", - " geometry\n", - " \n", + " geometry\n", "
                                                                      \n", " \n", " \n", @@ -1966,9 +1997,7 @@ " \n", " \n", "
                                                                    • \n", - " \n", - " assets\n", - " \n", + " assets\n", "
                                                                        \n", " \n", "
                                                                      \n", @@ -2093,8 +2122,7 @@ "source": [ "asset = pystac.Asset(href=img_path, media_type=pystac.MediaType.GEOTIFF)\n", "eo_item.add_asset(\"image\", asset)\n", - "eo_on_asset = EOExtension.ext(eo_item.assets[\"image\"])\n", - "eo_on_asset.apply(wv3_bands)" + "asset.ext.eo.bands = wv3_bands" ] }, { @@ -2124,11 +2152,9 @@ " font-weight: 700;\n", "}\n", ".pystac-key-value {\n", - " display: inline-block;\n", - " margin: 0px 0.5em 0px 0px;\n", + " display: inline-block;\n", + " margin: 0px 0.5em 0px 0px;\n", "}\n", - "\n", - "\n", "\n", "
                                                                      \n", "
                                                                      \n", @@ -2138,7 +2164,7 @@ " \n", "
                                                                    • \n", " href\n", - " \"/tmp/tmpbxjclo_s/image.tif\"\n", + " \"/tmp/tmpdsdpun_y/image.tif\"\n", "
                                                                    • \n", " \n", " \n", @@ -2164,9 +2190,7 @@ " \n", " \n", "
                                                                    • \n", - " \n", - " 0\n", - " \n", + " 0\n", "
                                                                        \n", " \n", " \n", @@ -2208,9 +2232,7 @@ " \n", " \n", "
                                                                      • \n", - " \n", - " 1\n", - " \n", + " 1\n", "
                                                                          \n", " \n", " \n", @@ -2252,9 +2274,7 @@ " \n", " \n", "
                                                                        • \n", - " \n", - " 2\n", - " \n", + " 2\n", "
                                                                            \n", " \n", " \n", @@ -2296,9 +2316,7 @@ " \n", " \n", "
                                                                          • \n", - " \n", - " 3\n", - " \n", + " 3\n", "
                                                                              \n", " \n", " \n", @@ -2340,9 +2358,7 @@ " \n", " \n", "
                                                                            • \n", - " \n", - " 4\n", - " \n", + " 4\n", "
                                                                                \n", " \n", " \n", @@ -2384,9 +2400,7 @@ " \n", " \n", "
                                                                              • \n", - " \n", - " 5\n", - " \n", + " 5\n", "
                                                                                  \n", " \n", " \n", @@ -2428,9 +2442,7 @@ " \n", " \n", "
                                                                                • \n", - " \n", - " 6\n", - " \n", + " 6\n", "
                                                                                    \n", " \n", " \n", @@ -2472,9 +2484,7 @@ " \n", " \n", "
                                                                                  • \n", - " \n", - " 7\n", - " \n", + " 7\n", "
                                                                                      \n", " \n", " \n", @@ -2519,7 +2529,7 @@ "
                                                                    • " ], "text/plain": [ - "" + "" ] }, "execution_count": 35, @@ -2644,7 +2654,7 @@ "metadata": {}, "outputs": [], "source": [ - "assert EOExtension.has_extension(item)" + "assert item.ext.has(\"eo\")" ] }, { @@ -2653,16 +2663,25 @@ "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "[, , , , , , , ]\n" - ] + "data": { + "text/plain": [ + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "eo_on_asset = EOExtension.ext(item.assets[\"image\"])\n", - "print(eo_on_asset.bands)" + "item.assets[\"image\"].ext.eo.bands" ] }, { @@ -2684,7 +2703,7 @@ { "data": { "text/plain": [ - "('/tmp/tmpbxjclo_s/image.tif', )" + "('/tmp/tmpdsdpun_y/image.tif', )" ] }, "execution_count": 44, @@ -2724,64 +2743,62 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "\u001b[0;31mInit signature:\u001b[0m\n", - "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCollection\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mid\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdescription\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextent\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Extent'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtitle\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstac_extensions\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mcatalog_type\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[CatalogType]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mlicense\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'proprietary'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mkeywords\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mproviders\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[Provider]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0msummaries\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Summaries]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0massets\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Asset]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m \n", - "A Collection extends the Catalog spec with additional metadata that helps\n", - "enable discovery.\n", - "\n", - "Args:\n", - " id : Identifier for the collection. Must be unique within the STAC.\n", - " description : Detailed multi-line description to fully explain the\n", - " collection. `CommonMark 0.29 syntax `_ MAY\n", - " be used for rich text representation.\n", - " extent : Spatial and temporal extents that describe the bounds of\n", - " all items contained within this Collection.\n", - " title : Optional short descriptive one-line title for the\n", - " collection.\n", - " stac_extensions : Optional list of extensions the Collection\n", - " implements.\n", - " href : Optional HREF for this collection, which be set as the\n", - " collection's self link's HREF.\n", - " catalog_type : Optional catalog type for this catalog. Must\n", - " be one of the values in :class`~pystac.CatalogType`.\n", - " license : Collection's license(s) as a\n", - " `SPDX License identifier `_,\n", - " `various`, or `proprietary`. If collection includes\n", - " data with multiple different licenses, use `various` and add a link for\n", - " each. Defaults to 'proprietary'.\n", - " keywords : Optional list of keywords describing the collection.\n", - " providers : Optional list of providers of this Collection.\n", - " summaries : An optional map of property summaries,\n", - " either a set of values or statistics such as a range.\n", - " extra_fields : Extra fields that are part of the top-level\n", - " JSON properties of the Collection.\n", - " assets : A dictionary mapping string keys to :class:`~pystac.Asset` objects. All\n", - " :class:`~pystac.Asset` values in the dictionary will have their\n", - " :attr:`~pystac.Asset.owner` attribute set to the created Collection.\n", - "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/collection.py\n", - "\u001b[0;31mType:\u001b[0m ABCMeta\n", - "\u001b[0;31mSubclasses:\u001b[0m " - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mInit signature:\u001b[0m\n", + "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCollection\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mid\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mdescription\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mextent\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Extent'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtitle\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mstac_extensions\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mhref\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mcatalog_type\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[CatalogType]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlicense\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'proprietary'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mkeywords\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mproviders\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[Provider]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0msummaries\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Summaries]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0massets\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Asset]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mDocstring:\u001b[0m \n", + "A Collection extends the Catalog spec with additional metadata that helps\n", + "enable discovery.\n", + "\n", + "Args:\n", + " id : Identifier for the collection. Must be unique within the STAC.\n", + " description : Detailed multi-line description to fully explain the\n", + " collection. `CommonMark 0.29 syntax `_ MAY\n", + " be used for rich text representation.\n", + " extent : Spatial and temporal extents that describe the bounds of\n", + " all items contained within this Collection.\n", + " title : Optional short descriptive one-line title for the\n", + " collection.\n", + " stac_extensions : Optional list of extensions the Collection\n", + " implements.\n", + " href : Optional HREF for this collection, which be set as the\n", + " collection's self link's HREF.\n", + " catalog_type : Optional catalog type for this catalog. Must\n", + " be one of the values in :class`~pystac.CatalogType`.\n", + " license : Collection's license(s) as a\n", + " `SPDX License identifier `_,\n", + " `various`, or `proprietary`. If collection includes\n", + " data with multiple different licenses, use `various` and add a link for\n", + " each. Defaults to 'proprietary'.\n", + " keywords : Optional list of keywords describing the collection.\n", + " providers : Optional list of providers of this Collection.\n", + " summaries : An optional map of property summaries,\n", + " either a set of values or statistics such as a range.\n", + " extra_fields : Extra fields that are part of the top-level\n", + " JSON properties of the Collection.\n", + " assets : A dictionary mapping string keys to :class:`~pystac.Asset` objects. All\n", + " :class:`~pystac.Asset` values in the dictionary will have their\n", + " :attr:`~pystac.Asset.owner` attribute set to the created Collection.\n", + "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/collection.py\n", + "\u001b[0;31mType:\u001b[0m ABCMeta\n", + "\u001b[0;31mSubclasses:\u001b[0m " + ] } ], "source": [ @@ -2801,29 +2818,27 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "\u001b[0;31mInit signature:\u001b[0m\n", - "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mExtent\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mspatial\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'SpatialExtent'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtemporal\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'TemporalExtent'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m \n", - "Describes the spatiotemporal extents of a Collection.\n", - "\n", - "Args:\n", - " spatial : Potential spatial extent covered by the collection.\n", - " temporal : Potential temporal extent covered by the collection.\n", - " extra_fields : Dictionary containing additional top-level fields defined on the\n", - " Extent object.\n", - "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/collection.py\n", - "\u001b[0;31mType:\u001b[0m type\n", - "\u001b[0;31mSubclasses:\u001b[0m " - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mInit signature:\u001b[0m\n", + "\u001b[0mpystac\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mExtent\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mspatial\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'SpatialExtent'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mtemporal\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'TemporalExtent'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mextra_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[Dict[str, Any]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mDocstring:\u001b[0m \n", + "Describes the spatiotemporal extents of a Collection.\n", + "\n", + "Args:\n", + " spatial : Potential spatial extent covered by the collection.\n", + " temporal : Potential temporal extent covered by the collection.\n", + " extra_fields : Dictionary containing additional top-level fields defined on the\n", + " Extent object.\n", + "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/collection.py\n", + "\u001b[0;31mType:\u001b[0m type\n", + "\u001b[0;31mSubclasses:\u001b[0m " + ] } ], "source": [ @@ -2859,8 +2874,8 @@ "\n", "asset = pystac.Asset(href=img_path, media_type=pystac.MediaType.GEOTIFF)\n", "collection_item.add_asset(\"image\", asset)\n", - "eo = EOExtension.ext(collection_item.assets[\"image\"], add_if_missing=True)\n", - "eo.apply(wv3_bands)\n", + "asset.ext.add(\"eo\")\n", + "asset.ext.eo.bands = wv3_bands\n", "\n", "collection_item2 = pystac.Item(\n", " id=\"local-image-col-2\",\n", @@ -2876,8 +2891,10 @@ "\n", "asset2 = pystac.Asset(href=img_path, media_type=pystac.MediaType.GEOTIFF)\n", "collection_item2.add_asset(\"image\", asset2)\n", - "eo = EOExtension.ext(collection_item2.assets[\"image\"], add_if_missing=True)\n", - "eo.apply([band for band in wv3_bands if band.name in [\"Red\", \"Green\", \"Blue\"]])" + "asset2.ext.add(\"eo\")\n", + "asset2.ext.eo.bands = [\n", + " band for band in wv3_bands if band.name in [\"Red\", \"Green\", \"Blue\"]\n", + "]" ] }, { @@ -2944,7 +2961,19 @@ "cell_type": "code", "execution_count": 53, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[>,\n", + " >]" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "collection.add_items([collection_item, collection_item2])" ] @@ -2953,7 +2982,70 @@ "cell_type": "code", "execution_count": 54, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "
                                                                      \n", + "
                                                                      \n", + "
                                                                        \n", + " \n", + " \n", + " \n", + "
                                                                      • \n", + " rel\n", + " \"child\"\n", + "
                                                                      • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                      • \n", + " href\n", + " \"./wv3-images/collection.json\"\n", + "
                                                                      • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                      • \n", + " type\n", + " \"application/json\"\n", + "
                                                                      • \n", + " \n", + " \n", + " \n", + "
                                                                      \n", + "
                                                                      \n", + "
                                                                      " + ], + "text/plain": [ + ">" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "catalog.clear_items()\n", "catalog.clear_children()\n", @@ -3052,7 +3144,7 @@ " self.s3 = boto3.resource(\"s3\")\n", "\n", " def read_text(self, source: Union[str, Link], *args: Any, **kwargs: Any) -> str:\n", - " parsed = urlparse(uri)\n", + " parsed = urlparse(source)\n", " if parsed.scheme == \"s3\":\n", " bucket = parsed.netloc\n", " key = parsed.path[1:]\n", @@ -3065,7 +3157,7 @@ " def write_text(\n", " self, dest: Union[str, Link], txt: str, *args: Any, **kwargs: Any\n", " ) -> None:\n", - " parsed = urlparse(uri)\n", + " parsed = urlparse(dest)\n", " if parsed.scheme == \"s3\":\n", " bucket = parsed.netloc\n", " key = parsed.path[1:]\n", @@ -3263,12 +3355,11 @@ " item.common_metadata.platform = \"Maxar\"\n", " item.common_metadata.instruments = [\"WorldView3\"]\n", "\n", - " eo = EOExtension.ext(item, add_if_missing=True)\n", - " eo.bands = wv3_bands\n", + " item.ext.add(\"eo\")\n", + " item.ext.eo.bands = wv3_bands\n", " asset = pystac.Asset(href=img_uri, media_type=pystac.MediaType.COG)\n", " item.add_asset(key=\"ps-ms\", asset=asset)\n", - " eo = EOExtension.ext(item.assets[\"ps-ms\"])\n", - " eo.bands = wv3_bands\n", + " asset.ext.eo.bands = wv3_bands\n", " chip_id_to_items[chip_id] = item" ] }, @@ -3331,7 +3422,27 @@ "cell_type": "code", "execution_count": 70, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[>,\n", + " >,\n", + " >,\n", + " >,\n", + " >,\n", + " >,\n", + " >,\n", + " >,\n", + " >,\n", + " >]" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "collection.add_items(chip_id_to_items.values())" ] @@ -3374,7 +3485,70 @@ "cell_type": "code", "execution_count": 72, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "
                                                                      \n", + "
                                                                      \n", + "
                                                                        \n", + " \n", + " \n", + " \n", + "
                                                                      • \n", + " rel\n", + " \"child\"\n", + "
                                                                      • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                      • \n", + " href\n", + " None\n", + "
                                                                      • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                      • \n", + " type\n", + " \"application/json\"\n", + "
                                                                      • \n", + " \n", + " \n", + " \n", + "
                                                                      \n", + "
                                                                      \n", + "
                                                                      " + ], + "text/plain": [ + ">" + ] + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "catalog = pystac.Catalog(id=\"spacenet5\", description=\"Spacenet 5 Data (Test)\")\n", "catalog.add_child(collection)" @@ -3454,7 +3628,70 @@ "cell_type": "code", "execution_count": 76, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "
                                                                      \n", + "
                                                                      \n", + "
                                                                        \n", + " \n", + " \n", + " \n", + "
                                                                      • \n", + " rel\n", + " \"child\"\n", + "
                                                                      • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                      • \n", + " href\n", + " None\n", + "
                                                                      • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                      • \n", + " type\n", + " \"application/json\"\n", + "
                                                                      • \n", + " \n", + " \n", + " \n", + "
                                                                      \n", + "
                                                                      \n", + "
                                                                      " + ], + "text/plain": [ + ">" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "label_catalog = pystac.Catalog(\n", " id=\"spacenet-data-labels\", description=\"Labels for Spacenet 5\"\n", @@ -3475,48 +3712,46 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "\u001b[0;31mSignature:\u001b[0m\n", - "\u001b[0mLabelExtension\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mlabel_description\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mlabel_type\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'LabelType'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mlabel_properties\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mlabel_classes\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[LabelClasses]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mlabel_tasks\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[Union[LabelTask, str]]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mlabel_methods\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[Union[LabelMethod, str]]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mlabel_overviews\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[LabelOverview]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m'None'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m\n", - "Applies label extension properties to the extended Item.\n", - "\n", - "Args:\n", - " label_description : A description of the label, how it was created,\n", - " and what it is recommended for\n", - " label_type : An Enum of either vector label type or raster label type. Use\n", - " one of :class:`~pystac.LabelType`.\n", - " label_properties : These are the names of the property field(s) in each\n", - " Feature of the label asset's FeatureCollection that contains the classes\n", - " (keywords from label:classes if the property defines classes).\n", - " If labels are rasters, this should be None.\n", - " label_classes : Optional, but required if using categorical data.\n", - " A list of :class:`LabelClasses` instances defining the list of possible\n", - " class names for each label:properties. (e.g., tree, building, car,\n", - " hippo)\n", - " label_tasks : Recommended to be a subset of 'regression', 'classification',\n", - " 'detection', or 'segmentation', but may be an arbitrary value.\n", - " label_methods: Recommended to be a subset of 'automated' or 'manual',\n", - " but may be an arbitrary value.\n", - " label_overviews : Optional list of :class:`LabelOverview` instances\n", - " that store counts (for classification-type data) or summary statistics\n", - " (for continuous numerical/regression data).\n", - "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/extensions/label.py\n", - "\u001b[0;31mType:\u001b[0m function" - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0;31mSignature:\u001b[0m\n", + "\u001b[0mLabelExtension\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlabel_description\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlabel_type\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'LabelType'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlabel_properties\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[str]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlabel_classes\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[LabelClasses]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlabel_tasks\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[Union[LabelTask, str]]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlabel_methods\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[Union[LabelMethod, str]]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m \u001b[0mlabel_overviews\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Optional[List[LabelOverview]]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", + "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m'None'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mDocstring:\u001b[0m\n", + "Applies label extension properties to the extended Item.\n", + "\n", + "Args:\n", + " label_description : A description of the label, how it was created,\n", + " and what it is recommended for\n", + " label_type : An Enum of either vector label type or raster label type. Use\n", + " one of :class:`~pystac.LabelType`.\n", + " label_properties : These are the names of the property field(s) in each\n", + " Feature of the label asset's FeatureCollection that contains the classes\n", + " (keywords from label:classes if the property defines classes).\n", + " If labels are rasters, this should be None.\n", + " label_classes : Optional, but required if using categorical data.\n", + " A list of :class:`LabelClasses` instances defining the list of possible\n", + " class names for each label:properties. (e.g., tree, building, car,\n", + " hippo)\n", + " label_tasks : Recommended to be a subset of 'regression', 'classification',\n", + " 'detection', or 'segmentation', but may be an arbitrary value.\n", + " label_methods: Recommended to be a subset of 'automated' or 'manual',\n", + " but may be an arbitrary value.\n", + " label_overviews : Optional list of :class:`LabelOverview` instances\n", + " that store counts (for classification-type data) or summary statistics\n", + " (for continuous numerical/regression data).\n", + "\u001b[0;31mFile:\u001b[0m ~/pystac/pystac/extensions/label.py\n", + "\u001b[0;31mType:\u001b[0m function" + ] } ], "source": [ @@ -3629,11 +3864,9 @@ " font-weight: 700;\n", "}\n", ".pystac-key-value {\n", - " display: inline-block;\n", - " margin: 0px 0.5em 0px 0px;\n", + " display: inline-block;\n", + " margin: 0px 0.5em 0px 0px;\n", "}\n", - "\n", - "\n", "\n", "
                                                                      \n", "
                                                                      \n", @@ -3669,9 +3902,7 @@ " \n", " \n", "
                                                                    • \n", - " \n", - " properties\n", - " \n", + " properties\n", "
                                                                        \n", " \n", " \n", @@ -3741,7 +3972,7 @@ " \n", "
                                                                      • \n", " datetime\n", - " \"2023-06-06T16:00:46.183290Z\"\n", + " \"2023-10-12T15:35:29.141767Z\"\n", "
                                                                      • \n", " \n", " \n", @@ -3754,9 +3985,7 @@ " \n", " \n", "
                                                                      • \n", - " \n", - " geometry\n", - " \n", + " geometry\n", "
                                                                          \n", " \n", " \n", @@ -4009,9 +4238,7 @@ " \n", " \n", "
                                                                        • \n", - " \n", - " 0\n", - " \n", + " 0\n", "
                                                                            \n", " \n", " \n", @@ -4053,9 +4280,7 @@ " \n", " \n", "
                                                                          • \n", - " \n", - " 1\n", - " \n", + " 1\n", "
                                                                              \n", " \n", " \n", @@ -4097,9 +4322,7 @@ " \n", " \n", "
                                                                            • \n", - " \n", - " 2\n", - " \n", + " 2\n", "
                                                                                \n", " \n", " \n", @@ -4142,17 +4365,13 @@ " \n", " \n", "
                                                                              • \n", - " \n", - " assets\n", - " \n", + " assets\n", "
                                                                                  \n", " \n", " \n", " \n", "
                                                                                • \n", - " \n", - " labels\n", - " \n", + " labels\n", "
                                                                                    \n", " \n", " \n", @@ -4303,7 +4522,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.15" + "version": "3.11.6" } }, "nbformat": 4, diff --git a/docs/tutorials/pystac-introduction.ipynb b/docs/tutorials/pystac-introduction.ipynb index 84f1de6dd..d53afcfcb 100644 --- a/docs/tutorials/pystac-introduction.ipynb +++ b/docs/tutorials/pystac-introduction.ipynb @@ -1914,7 +1914,70 @@ "cell_type": "code", "execution_count": 22, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "
                                                                                    \n", + "
                                                                                    \n", + "
                                                                                      \n", + " \n", + " \n", + " \n", + "
                                                                                    • \n", + " rel\n", + " \"child\"\n", + "
                                                                                    • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                                    • \n", + " href\n", + " None\n", + "
                                                                                    • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                                    • \n", + " type\n", + " \"application/json\"\n", + "
                                                                                    • \n", + " \n", + " \n", + " \n", + "
                                                                                    \n", + "
                                                                                    \n", + "
                                                                                    " + ], + "text/plain": [ + ">" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "mycat.add_child(kitten)" ] @@ -3378,7 +3441,79 @@ "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "
                                                                                    \n", + "
                                                                                    \n", + "
                                                                                      \n", + " \n", + " \n", + " \n", + "
                                                                                    • \n", + " rel\n", + " \"child\"\n", + "
                                                                                    • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                                    • \n", + " href\n", + " \"/home/jsignell/pystac/docs/example-catalog/landsat-8-l1/collection.json\"\n", + "
                                                                                    • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                                    • \n", + " type\n", + " \"application/json\"\n", + "
                                                                                    • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                                    • \n", + " title\n", + " \"Landsat 8 L1\"\n", + "
                                                                                    • \n", + " \n", + " \n", + " \n", + "
                                                                                    \n", + "
                                                                                    \n", + "
                                                                                    " + ], + "text/plain": [ + ">" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# add it to the child catalog created above\n", "kitten.add_child(collection)" @@ -6023,7 +6158,70 @@ "cell_type": "code", "execution_count": 34, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "
                                                                                    \n", + "
                                                                                    \n", + "
                                                                                      \n", + " \n", + " \n", + " \n", + "
                                                                                    • \n", + " rel\n", + " \"item\"\n", + "
                                                                                    • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                                    • \n", + " href\n", + " \"/home/jsignell/pystac/docs/example-catalog/landsat-8-l1/LC80150322018141LGN00/LC80150322018141LGN00.json\"\n", + "
                                                                                    • \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
                                                                                    • \n", + " type\n", + " \"application/json\"\n", + "
                                                                                    • \n", + " \n", + " \n", + " \n", + "
                                                                                    \n", + "
                                                                                    \n", + "
                                                                                    " + ], + "text/plain": [ + ">" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# add it to the collection created above\n", "collection.add_item(item)" @@ -6156,7 +6354,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.15" + "version": "3.11.6" } }, "nbformat": 4, diff --git a/pystac/asset.py b/pystac/asset.py index cbcf2c039..4c3b48f7b 100644 --- a/pystac/asset.py +++ b/pystac/asset.py @@ -12,6 +12,7 @@ if TYPE_CHECKING: from pystac.collection import Collection from pystac.common_metadata import CommonMetadata + from pystac.extensions.ext import AssetExt from pystac.item import Item A = TypeVar("A", bound="Asset") @@ -261,6 +262,18 @@ def delete(self) -> None: href = _absolute_href(self.href, self.owner, "delete") os.remove(href) + @property + def ext(self) -> AssetExt: + """Accessor for extension classes on this asset + + Example:: + + asset.ext.proj.epsg = 4326 + """ + from pystac.extensions.ext import AssetExt + + return AssetExt(stac_object=self) + def _absolute_href( href: str, owner: Optional[Union[Item, Collection]], action: str = "access" diff --git a/pystac/collection.py b/pystac/collection.py index 766bd27a8..f8746dc09 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -42,6 +42,7 @@ ) if TYPE_CHECKING: + from pystac.extensions.ext import CollectionExt from pystac.item import Item C = TypeVar("C", bound="Collection") @@ -830,3 +831,15 @@ def full_copy( @classmethod def matches_object_type(cls, d: Dict[str, Any]) -> bool: return identify_stac_object_type(d) == STACObjectType.COLLECTION + + @property + def ext(self) -> CollectionExt: + """Accessor for extension classes on this collection + + Example:: + + print(collection.ext.xarray) + """ + from pystac.extensions.ext import CollectionExt + + return CollectionExt(stac_object=self) diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index 12d29ce37..f10f2e6e3 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import re import warnings from abc import ABC, abstractmethod from typing import ( + TYPE_CHECKING, Any, Dict, Generic, @@ -16,6 +19,9 @@ import pystac +if TYPE_CHECKING: + from pystac.extensions.item_assets import AssetDefinition + VERSION_REGEX = re.compile("/v[0-9].[0-9].*/") @@ -157,7 +163,39 @@ def has_extension(cls, obj: S) -> bool: @classmethod def validate_owner_has_extension( cls, - asset: pystac.Asset, + asset: Union[pystac.Asset, AssetDefinition], + add_if_missing: bool = False, + ) -> None: + """ + DEPRECATED + + .. deprecated:: 1.9 + Use :meth:`ensure_owner_has_extension` instead. + + Given an :class:`~pystac.Asset`, checks if the asset's owner has this + extension's schema URI in its :attr:`~pystac.STACObject.stac_extensions` list. + If ``add_if_missing`` is ``True``, the schema URI will be added to the owner. + + Args: + asset : The asset whose owner should be validated. + add_if_missing : Whether to add the schema URI to the owner if it is + not already present. Defaults to False. + + Raises: + STACError : If ``add_if_missing`` is ``True`` and ``asset.owner`` is + ``None``. + """ + warnings.warn( + "ensure_owner_has_extension is deprecated and will be removed in v1.9. " + "Use ensure_owner_has_extension instead", + DeprecationWarning, + ) + return cls.ensure_owner_has_extension(asset, add_if_missing) + + @classmethod + def ensure_owner_has_extension( + cls, + asset: Union[pystac.Asset, AssetDefinition], add_if_missing: bool = False, ) -> None: """Given an :class:`~pystac.Asset`, checks if the asset's owner has this @@ -176,8 +214,8 @@ def validate_owner_has_extension( if asset.owner is None: if add_if_missing: raise pystac.STACError( - "Attempted to use add_if_missing=True for an Asset with no owner. " - "Use Asset.set_owner or set add_if_missing=False." + "Attempted to use add_if_missing=True for an Asset or ItemAsset " + "with no owner. Use .set_owner or set add_if_missing=False." ) else: return @@ -223,8 +261,16 @@ def ensure_has_extension(cls, obj: S, add_if_missing: bool = False) -> None: cls.add_to(obj) if not cls.has_extension(obj): + name = getattr(cls, "name", cls.__name__) + suggestion = ( + f"``obj.ext.add('{name}')" + if hasattr(cls, "name") + else f"``{name}.add_to(obj)``" + ) + raise pystac.ExtensionNotImplemented( - f"Could not find extension schema URI {cls.get_schema_uri()} in object." + f"Extension '{name}' is not implemented on object." + f"STAC producers can add the extension using {suggestion}" ) @classmethod diff --git a/pystac/extensions/classification.py b/pystac/extensions/classification.py index 3705fea3f..4407510b9 100644 --- a/pystac/extensions/classification.py +++ b/pystac/extensions/classification.py @@ -10,6 +10,7 @@ Generic, Iterable, List, + Literal, Optional, Pattern, TypeVar, @@ -18,7 +19,7 @@ ) import pystac -import pystac.extensions.item_assets as item_assets +from pystac.extensions import item_assets from pystac.extensions.base import ( ExtensionManagementMixin, PropertiesExtension, @@ -426,6 +427,7 @@ class ClassificationExtension( method can be used to construct the proper class for you. """ + name: Literal["classification"] = "classification" properties: Dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" @@ -534,12 +536,10 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ClassificationExtension[T] cls.ensure_has_extension(obj, add_if_missing) return cast(ClassificationExtension[T], ItemClassificationExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(ClassificationExtension[T], AssetClassificationExtension(obj)) elif isinstance(obj, item_assets.AssetDefinition): - cls.ensure_has_extension( - cast(Union[pystac.Item, pystac.Collection], obj.owner), add_if_missing - ) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast( ClassificationExtension[T], ItemAssetsClassificationExtension(obj) ) diff --git a/pystac/extensions/datacube.py b/pystac/extensions/datacube.py index d051301a9..eee9ad473 100644 --- a/pystac/extensions/datacube.py +++ b/pystac/extensions/datacube.py @@ -3,14 +3,17 @@ from __future__ import annotations from abc import ABC -from typing import Any, Dict, Generic, List, Optional, TypeVar, Union, cast +from typing import Any, Dict, Generic, List, Literal, Optional, TypeVar, Union, cast import pystac +from pystac.extensions import item_assets from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension from pystac.extensions.hooks import ExtensionHooks from pystac.utils import StringEnum, get_required, map_opt -T = TypeVar("T", pystac.Collection, pystac.Item, pystac.Asset) +T = TypeVar( + "T", pystac.Collection, pystac.Item, pystac.Asset, item_assets.AssetDefinition +) SCHEMA_URI = "https://stac-extensions.github.io/datacube/v2.0.0/schema.json" @@ -469,6 +472,8 @@ class DatacubeExtension( >>> dc_ext = DatacubeExtension.ext(item) """ + name: Literal["cube"] = "cube" + def apply( self, dimensions: Dict[str, Dimension], @@ -543,8 +548,11 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> DatacubeExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(DatacubeExtension[T], ItemDatacubeExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(DatacubeExtension[T], AssetDatacubeExtension(obj)) + elif isinstance(obj, item_assets.AssetDefinition): + cls.ensure_owner_has_extension(obj, add_if_missing) + return cast(DatacubeExtension[T], ItemAssetsDatacubeExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) @@ -614,6 +622,15 @@ def __repr__(self) -> str: return "".format(self.asset_href) +class ItemAssetsDatacubeExtension(DatacubeExtension[item_assets.AssetDefinition]): + properties: Dict[str, Any] + asset_defn: item_assets.AssetDefinition + + def __init__(self, item_asset: item_assets.AssetDefinition): + self.asset_defn = item_asset + self.properties = item_asset.properties + + class DatacubeExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids = { diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index f2e81faa8..dc4b7b326 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -9,6 +9,7 @@ Generic, Iterable, List, + Literal, Optional, Tuple, TypeVar, @@ -17,7 +18,7 @@ ) import pystac -from pystac.extensions import projection, view +from pystac.extensions import item_assets, projection, view from pystac.extensions.base import ( ExtensionManagementMixin, PropertiesExtension, @@ -28,7 +29,7 @@ from pystac.summaries import RangeSummary from pystac.utils import get_required, map_opt -T = TypeVar("T", pystac.Item, pystac.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition) SCHEMA_URI: str = "https://stac-extensions.github.io/eo/v1.1.0/schema.json" SCHEMA_URIS: List[str] = [ @@ -309,6 +310,8 @@ class EOExtension( >>> eo_ext = EOExtension.ext(item) """ + name: Literal["eo"] = "eo" + def apply( self, bands: Optional[List[Band]] = None, @@ -408,8 +411,11 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> EOExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(EOExtension[T], ItemEOExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(EOExtension[T], AssetEOExtension(obj)) + elif isinstance(obj, item_assets.AssetDefinition): + cls.ensure_owner_has_extension(obj, add_if_missing) + return cast(EOExtension[T], ItemAssetsEOExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) @@ -534,6 +540,25 @@ def __repr__(self) -> str: return "".format(self.asset_href) +class ItemAssetsEOExtension(EOExtension[item_assets.AssetDefinition]): + properties: Dict[str, Any] + asset_defn: item_assets.AssetDefinition + + def _get_bands(self) -> Optional[List[Band]]: + if BANDS_PROP not in self.properties: + return None + return list( + map( + lambda band: Band(band), + cast(List[Dict[str, Any]], self.properties.get(BANDS_PROP)), + ) + ) + + def __init__(self, item_asset: item_assets.AssetDefinition): + self.asset_defn = item_asset + self.properties = item_asset.properties + + class SummariesEOExtension(SummariesExtension): """A concrete implementation of :class:`~SummariesExtension` that extends the ``summaries`` field of a :class:`~pystac.Collection` to include properties diff --git a/pystac/extensions/ext.py b/pystac/extensions/ext.py new file mode 100644 index 000000000..93a30fee5 --- /dev/null +++ b/pystac/extensions/ext.py @@ -0,0 +1,290 @@ +from dataclasses import dataclass +from typing import Any, Dict, Generic, Literal, TypeVar, cast + +import pystac +from pystac.extensions.classification import ClassificationExtension +from pystac.extensions.datacube import DatacubeExtension +from pystac.extensions.eo import EOExtension +from pystac.extensions.file import FileExtension +from pystac.extensions.grid import GridExtension +from pystac.extensions.item_assets import AssetDefinition, ItemAssetsExtension +from pystac.extensions.mgrs import MgrsExtension +from pystac.extensions.pointcloud import PointcloudExtension +from pystac.extensions.projection import ProjectionExtension +from pystac.extensions.raster import RasterExtension +from pystac.extensions.sar import SarExtension +from pystac.extensions.sat import SatExtension +from pystac.extensions.scientific import ScientificExtension +from pystac.extensions.storage import StorageExtension +from pystac.extensions.table import TableExtension +from pystac.extensions.timestamps import TimestampsExtension +from pystac.extensions.version import VersionExtension +from pystac.extensions.view import ViewExtension +from pystac.extensions.xarray_assets import XarrayAssetsExtension + +T = TypeVar("T", pystac.Asset, AssetDefinition) + +EXTENSION_NAMES = Literal[ + "classification", + "cube", + "eo", + "file", + "grid", + "item_assets", + "mgrs", + "pc", + "proj", + "raster", + "sar", + "sat", + "sci", + "storage", + "table", + "timestamps", + "version", + "view", + "xarray", +] + +EXTENSION_NAME_MAPPING: Dict[EXTENSION_NAMES, Any] = { + ClassificationExtension.name: ClassificationExtension, + DatacubeExtension.name: DatacubeExtension, + EOExtension.name: EOExtension, + FileExtension.name: FileExtension, + GridExtension.name: GridExtension, + ItemAssetsExtension.name: ItemAssetsExtension, + MgrsExtension.name: MgrsExtension, + PointcloudExtension.name: PointcloudExtension, + ProjectionExtension.name: ProjectionExtension, + RasterExtension.name: RasterExtension, + SarExtension.name: SarExtension, + SatExtension.name: SatExtension, + ScientificExtension.name: ScientificExtension, + StorageExtension.name: StorageExtension, + TableExtension.name: TableExtension, + TimestampsExtension.name: TimestampsExtension, + VersionExtension.name: VersionExtension, + ViewExtension.name: ViewExtension, + XarrayAssetsExtension.name: XarrayAssetsExtension, +} + + +def _get_class_by_name(name: str) -> Any: + try: + return EXTENSION_NAME_MAPPING[cast(EXTENSION_NAMES, name)] + except KeyError as e: + raise KeyError( + f"Extension '{name}' is not a valid extension. " + f"Options are {list(EXTENSION_NAME_MAPPING)}" + ) from e + + +@dataclass +class CollectionExt: + stac_object: pystac.Collection + + def has(self, name: EXTENSION_NAMES) -> bool: + return cast(bool, _get_class_by_name(name).has_extension(self.stac_object)) + + def add(self, name: EXTENSION_NAMES) -> None: + _get_class_by_name(name).add_to(self.stac_object) + + def remove(self, name: EXTENSION_NAMES) -> None: + _get_class_by_name(name).remove_from(self.stac_object) + + @property + def cube(self) -> DatacubeExtension[pystac.Collection]: + return DatacubeExtension.ext(self.stac_object) + + @property + def item_assets(self) -> Dict[str, AssetDefinition]: + return ItemAssetsExtension.ext(self.stac_object).item_assets + + @property + def sci(self) -> ScientificExtension[pystac.Collection]: + return ScientificExtension.ext(self.stac_object) + + @property + def table(self) -> TableExtension[pystac.Collection]: + return TableExtension.ext(self.stac_object) + + @property + def version(self) -> VersionExtension[pystac.Collection]: + return VersionExtension.ext(self.stac_object) + + @property + def xarray(self) -> XarrayAssetsExtension[pystac.Collection]: + return XarrayAssetsExtension.ext(self.stac_object) + + +@dataclass +class ItemExt: + stac_object: pystac.Item + + def has(self, name: EXTENSION_NAMES) -> bool: + return cast(bool, _get_class_by_name(name).has_extension(self.stac_object)) + + def add(self, name: EXTENSION_NAMES) -> None: + _get_class_by_name(name).add_to(self.stac_object) + + def remove(self, name: EXTENSION_NAMES) -> None: + _get_class_by_name(name).remove_from(self.stac_object) + + @property + def classification(self) -> ClassificationExtension[pystac.Item]: + return ClassificationExtension.ext(self.stac_object) + + @property + def cube(self) -> DatacubeExtension[pystac.Item]: + return DatacubeExtension.ext(self.stac_object) + + @property + def eo(self) -> EOExtension[pystac.Item]: + return EOExtension.ext(self.stac_object) + + @property + def grid(self) -> GridExtension: + return GridExtension.ext(self.stac_object) + + @property + def mgrs(self) -> MgrsExtension: + return MgrsExtension.ext(self.stac_object) + + @property + def pc(self) -> PointcloudExtension[pystac.Item]: + return PointcloudExtension.ext(self.stac_object) + + @property + def proj(self) -> ProjectionExtension[pystac.Item]: + return ProjectionExtension.ext(self.stac_object) + + @property + def sar(self) -> SarExtension[pystac.Item]: + return SarExtension.ext(self.stac_object) + + @property + def sat(self) -> SatExtension[pystac.Item]: + return SatExtension.ext(self.stac_object) + + @property + def storage(self) -> StorageExtension[pystac.Item]: + return StorageExtension.ext(self.stac_object) + + @property + def table(self) -> TableExtension[pystac.Item]: + return TableExtension.ext(self.stac_object) + + @property + def timestamps(self) -> TimestampsExtension[pystac.Item]: + return TimestampsExtension.ext(self.stac_object) + + @property + def version(self) -> VersionExtension[pystac.Item]: + return VersionExtension.ext(self.stac_object) + + @property + def view(self) -> ViewExtension[pystac.Item]: + return ViewExtension.ext(self.stac_object) + + @property + def xarray(self) -> XarrayAssetsExtension[pystac.Item]: + return XarrayAssetsExtension.ext(self.stac_object) + + +class _AssetExt(Generic[T]): + stac_object: T + + def has(self, name: EXTENSION_NAMES) -> bool: + if self.stac_object.owner is None: + raise pystac.STACError( + f"Attempted to add extension='{name}' for an Asset with no owner. " + "Use Asset.set_owner and then try to add the extension again." + ) + else: + return cast( + bool, _get_class_by_name(name).has_extension(self.stac_object.owner) + ) + + def add(self, name: EXTENSION_NAMES) -> None: + if self.stac_object.owner is None: + raise pystac.STACError( + f"Attempted to add extension='{name}' for an Asset with no owner. " + "Use Asset.set_owner and then try to add the extension again." + ) + else: + _get_class_by_name(name).add_to(self.stac_object.owner) + + def remove(self, name: EXTENSION_NAMES) -> None: + if self.stac_object.owner is None: + raise pystac.STACError( + f"Attempted to remove extension='{name}' for an Asset with no owner. " + "Use Asset.set_owner and then try to remove the extension again." + ) + else: + _get_class_by_name(name).remove_from(self.stac_object.owner) + + @property + def classification(self) -> ClassificationExtension[T]: + return ClassificationExtension.ext(self.stac_object) + + @property + def cube(self) -> DatacubeExtension[T]: + return DatacubeExtension.ext(self.stac_object) + + @property + def eo(self) -> EOExtension[T]: + return EOExtension.ext(self.stac_object) + + @property + def pc(self) -> PointcloudExtension[T]: + return PointcloudExtension.ext(self.stac_object) + + @property + def proj(self) -> ProjectionExtension[T]: + return ProjectionExtension.ext(self.stac_object) + + @property + def raster(self) -> RasterExtension[T]: + return RasterExtension.ext(self.stac_object) + + @property + def sar(self) -> SarExtension[T]: + return SarExtension.ext(self.stac_object) + + @property + def sat(self) -> SatExtension[T]: + return SatExtension.ext(self.stac_object) + + @property + def storage(self) -> StorageExtension[T]: + return StorageExtension.ext(self.stac_object) + + @property + def table(self) -> TableExtension[T]: + return TableExtension.ext(self.stac_object) + + @property + def view(self) -> ViewExtension[T]: + return ViewExtension.ext(self.stac_object) + + +@dataclass +class AssetExt(_AssetExt[pystac.Asset]): + stac_object: pystac.Asset + + @property + def file(self) -> FileExtension: + return FileExtension.ext(self.stac_object) + + @property + def timestamps(self) -> TimestampsExtension[pystac.Asset]: + return TimestampsExtension.ext(self.stac_object) + + @property + def xarray(self) -> XarrayAssetsExtension[pystac.Asset]: + return XarrayAssetsExtension.ext(self.stac_object) + + +@dataclass +class ItemAssetExt(_AssetExt[AssetDefinition]): + stac_object: AssetDefinition diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 921f94352..1d76d228a 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any, Dict, Iterable, List, Optional, Union +from typing import Any, Dict, Iterable, List, Literal, Optional, Union import pystac from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension @@ -91,7 +91,7 @@ def to_dict(self) -> Dict[str, Any]: class FileExtension( - PropertiesExtension, ExtensionManagementMixin[Union[pystac.Item, pystac.Collection]] + PropertiesExtension, ExtensionManagementMixin[Union[pystac.Collection, pystac.Item]] ): """A class that can be used to extend the properties of an :class:`~pystac.Asset` with properties from the :stac-ext:`File Info Extension `. @@ -105,6 +105,8 @@ class FileExtension( >>> file_ext = FileExtension.ext(asset) """ + name: Literal["file"] = "file" + asset_href: str """The ``href`` value of the :class:`~pystac.Asset` being extended.""" @@ -224,7 +226,7 @@ def ext(cls, obj: pystac.Asset, add_if_missing: bool = False) -> FileExtension: This extension can be applied to instances of :class:`~pystac.Asset`. """ if isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cls(obj) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/grid.py b/pystac/extensions/grid.py index e40b61410..e977e1cab 100644 --- a/pystac/extensions/grid.py +++ b/pystac/extensions/grid.py @@ -4,7 +4,7 @@ import re import warnings -from typing import Any, Dict, List, Optional, Pattern, Set, Union +from typing import Any, Dict, List, Literal, Optional, Pattern, Set, Union import pystac from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension @@ -51,6 +51,8 @@ class GridExtension( >>> proj_ext = GridExtension.ext(item) """ + name: Literal["grid"] = "grid" + item: pystac.Item """The :class:`~pystac.Item` being extended.""" diff --git a/pystac/extensions/item_assets.py b/pystac/extensions/item_assets.py index 861883bb7..33f9b6627 100644 --- a/pystac/extensions/item_assets.py +++ b/pystac/extensions/item_assets.py @@ -3,7 +3,7 @@ from __future__ import annotations from copy import deepcopy -from typing import Any, Dict, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional import pystac from pystac.extensions.base import ExtensionManagementMixin @@ -11,6 +11,9 @@ from pystac.serialization.identify import STACJSONDescription, STACVersionID from pystac.utils import get_required +if TYPE_CHECKING: + from pystac.extensions.ext import ItemAssetExt + SCHEMA_URI = "https://stac-extensions.github.io/item-assets/v1.0.0/schema.json" ITEM_ASSETS_PROP = "item_assets" @@ -204,8 +207,21 @@ def create_asset(self, href: str) -> pystac.Asset: }, ) + @property + def ext(self) -> ItemAssetExt: + """Accessor for extension classes on this item_asset + + Example:: + + collection.ext.item_assets["data"].ext.proj.epsg = 4326 + """ + from pystac.extensions.ext import ItemAssetExt + + return ItemAssetExt(stac_object=self) + class ItemAssetsExtension(ExtensionManagementMixin[pystac.Collection]): + name: Literal["item_assets"] = "item_assets" collection: pystac.Collection def __init__(self, collection: pystac.Collection) -> None: diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index 68e13a489..1dbd65fbe 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -3,7 +3,7 @@ from __future__ import annotations import warnings -from typing import Any, Dict, Iterable, List, Optional, Sequence, Union, cast +from typing import Any, Dict, Iterable, List, Literal, Optional, Sequence, Union, cast import pystac from pystac.extensions.base import ExtensionManagementMixin, SummariesExtension @@ -447,6 +447,7 @@ class LabelExtension(ExtensionManagementMixin[Union[pystac.Item, pystac.Collecti >>> label_ext = LabelExtension.ext(item) """ + name: Literal["label"] = "label" obj: pystac.Item schema_uri: str diff --git a/pystac/extensions/mgrs.py b/pystac/extensions/mgrs.py index 658c9bde8..ea8aeed96 100644 --- a/pystac/extensions/mgrs.py +++ b/pystac/extensions/mgrs.py @@ -1,7 +1,7 @@ """Implements the :stac-ext:`MGRS Extension `.""" import re -from typing import Any, Dict, FrozenSet, Optional, Pattern, Set, Union +from typing import Any, Dict, FrozenSet, Literal, Optional, Pattern, Set, Union import pystac from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension @@ -156,6 +156,7 @@ class MgrsExtension( >>> proj_ext = MgrsExtension.ext(item) """ + name: Literal["mgrs"] = "mgrs" item: pystac.Item """The :class:`~pystac.Item` being extended.""" diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index b197128c7..50af76789 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -2,9 +2,21 @@ from __future__ import annotations -from typing import Any, Dict, Generic, Iterable, List, Optional, TypeVar, Union, cast +from typing import ( + Any, + Dict, + Generic, + Iterable, + List, + Literal, + Optional, + TypeVar, + Union, + cast, +) import pystac +from pystac.extensions import item_assets from pystac.extensions.base import ( ExtensionManagementMixin, PropertiesExtension, @@ -14,7 +26,7 @@ from pystac.summaries import RangeSummary from pystac.utils import StringEnum, get_required, map_opt -T = TypeVar("T", pystac.Item, pystac.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition) SCHEMA_URI: str = "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" PREFIX: str = "pc:" @@ -338,6 +350,8 @@ class PointcloudExtension( >>> pc_ext = PointcloudExtension.ext(item) """ + name: Literal["pc"] = "pc" + def apply( self, count: int, @@ -455,8 +469,11 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> PointcloudExtension[T]: raise pystac.ExtensionTypeError( "Pointcloud extension does not apply to Collection Assets." ) - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(PointcloudExtension[T], AssetPointcloudExtension(obj)) + elif isinstance(obj, item_assets.AssetDefinition): + cls.ensure_owner_has_extension(obj, add_if_missing) + return cast(PointcloudExtension[T], ItemAssetsPointcloudExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) @@ -520,6 +537,15 @@ def __repr__(self) -> str: return f"" +class ItemAssetsPointcloudExtension(PointcloudExtension[item_assets.AssetDefinition]): + properties: Dict[str, Any] + asset_defn: item_assets.AssetDefinition + + def __init__(self, item_asset: item_assets.AssetDefinition): + self.asset_defn = item_asset + self.properties = item_asset.properties + + class SummariesPointcloudExtension(SummariesExtension): """A concrete implementation of :class:`~SummariesExtension` that extends the ``summaries`` field of a :class:`~pystac.Collection` to include properties diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index 0a29c0680..af24903e1 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -4,9 +4,21 @@ import json import warnings -from typing import Any, Dict, Generic, Iterable, List, Optional, TypeVar, Union, cast +from typing import ( + Any, + Dict, + Generic, + Iterable, + List, + Literal, + Optional, + TypeVar, + Union, + cast, +) import pystac +from pystac.extensions import item_assets from pystac.extensions.base import ( ExtensionManagementMixin, PropertiesExtension, @@ -14,7 +26,7 @@ ) from pystac.extensions.hooks import ExtensionHooks -T = TypeVar("T", pystac.Item, pystac.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition) SCHEMA_URI: str = "https://stac-extensions.github.io/projection/v1.1.0/schema.json" SCHEMA_URIS: List[str] = [ @@ -53,6 +65,8 @@ class ProjectionExtension( >>> proj_ext = ProjectionExtension.ext(item) """ + name: Literal["proj"] = "proj" + def apply( self, epsg: Optional[int], @@ -288,8 +302,11 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ProjectionExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(ProjectionExtension[T], ItemProjectionExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(ProjectionExtension[T], AssetProjectionExtension(obj)) + elif isinstance(obj, item_assets.AssetDefinition): + cls.ensure_owner_has_extension(obj, add_if_missing) + return cast(ProjectionExtension[T], ItemAssetsProjectionExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) @@ -354,6 +371,15 @@ def __repr__(self) -> str: return "".format(self.asset_href) +class ItemAssetsProjectionExtension(ProjectionExtension[item_assets.AssetDefinition]): + properties: Dict[str, Any] + asset_defn: item_assets.AssetDefinition + + def __init__(self, item_asset: item_assets.AssetDefinition): + self.asset_defn = item_asset + self.properties = item_asset.properties + + class SummariesProjectionExtension(SummariesExtension): """A concrete implementation of :class:`~SummariesExtension` that extends the ``summaries`` field of a :class:`~pystac.Collection` to include properties diff --git a/pystac/extensions/raster.py b/pystac/extensions/raster.py index 5a7115474..9cba893fb 100644 --- a/pystac/extensions/raster.py +++ b/pystac/extensions/raster.py @@ -9,6 +9,7 @@ Generic, Iterable, List, + Literal, Optional, Set, TypeVar, @@ -17,7 +18,7 @@ ) import pystac -import pystac.extensions.item_assets as item_assets +from pystac.extensions import item_assets from pystac.extensions.base import ( ExtensionManagementMixin, PropertiesExtension, @@ -678,6 +679,8 @@ class RasterExtension( instance type for you. """ + name: Literal["raster"] = "raster" + properties: Dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" @@ -735,12 +738,10 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> RasterExtension[T]: pystac.ExtensionTypeError : If an invalid object type is passed. """ if isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(RasterExtension[T], AssetRasterExtension(obj)) elif isinstance(obj, item_assets.AssetDefinition): - cls.ensure_has_extension( - cast(Union[pystac.Item, pystac.Collection], obj.owner), add_if_missing - ) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(RasterExtension[T], ItemAssetsRasterExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index 7afec35a7..4138c812d 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -2,9 +2,21 @@ from __future__ import annotations -from typing import Any, Dict, Generic, Iterable, List, Optional, TypeVar, Union, cast +from typing import ( + Any, + Dict, + Generic, + Iterable, + List, + Literal, + Optional, + TypeVar, + Union, + cast, +) import pystac +from pystac.extensions import item_assets from pystac.extensions.base import ( ExtensionManagementMixin, PropertiesExtension, @@ -15,7 +27,7 @@ from pystac.summaries import RangeSummary from pystac.utils import StringEnum, get_required, map_opt -T = TypeVar("T", pystac.Item, pystac.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition) SCHEMA_URI: str = "https://stac-extensions.github.io/sar/v1.0.0/schema.json" PREFIX: str = "sar:" @@ -81,6 +93,8 @@ class SarExtension( >>> sar_ext = SARExtension.ext(item) """ + name: Literal["sar"] = "sar" + def apply( self, instrument_mode: str, @@ -319,8 +333,11 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> SarExtension[T]: raise pystac.ExtensionTypeError( "SAR extension does not apply to Collection Assets." ) - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(SarExtension[T], AssetSarExtension(obj)) + elif isinstance(obj, item_assets.AssetDefinition): + cls.ensure_owner_has_extension(obj, add_if_missing) + return cast(SarExtension[T], ItemAssetsSarExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) @@ -385,6 +402,15 @@ def __repr__(self) -> str: return "".format(self.asset_href) +class ItemAssetsSarExtension(SarExtension[item_assets.AssetDefinition]): + properties: Dict[str, Any] + asset_defn: item_assets.AssetDefinition + + def __init__(self, item_asset: item_assets.AssetDefinition): + self.asset_defn = item_asset + self.properties = item_asset.properties + + class SummariesSarExtension(SummariesExtension): """A concrete implementation of :class:`~SummariesExtension` that extends the ``summaries`` field of a :class:`~pystac.Collection` to include properties diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 8d89ca64a..0d286edde 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -3,9 +3,21 @@ from __future__ import annotations from datetime import datetime -from typing import Any, Dict, Generic, Iterable, List, Optional, TypeVar, Union, cast +from typing import ( + Any, + Dict, + Generic, + Iterable, + List, + Literal, + Optional, + TypeVar, + Union, + cast, +) import pystac +from pystac.extensions import item_assets from pystac.extensions.base import ( ExtensionManagementMixin, PropertiesExtension, @@ -15,7 +27,7 @@ from pystac.summaries import RangeSummary from pystac.utils import StringEnum, datetime_to_str, map_opt, str_to_datetime -T = TypeVar("T", pystac.Item, pystac.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition) SCHEMA_URI = "https://stac-extensions.github.io/sat/v1.0.0/schema.json" @@ -55,6 +67,8 @@ class SatExtension( >>> sat_ext = SatExtension.ext(item) """ + name: Literal["sat"] = "sat" + def apply( self, orbit_state: Optional[OrbitState] = None, @@ -150,8 +164,11 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> SatExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(SatExtension[T], ItemSatExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(SatExtension[T], AssetSatExtension(obj)) + elif isinstance(obj, item_assets.AssetDefinition): + cls.ensure_owner_has_extension(obj, add_if_missing) + return cast(SatExtension[T], ItemAssetsSatExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) @@ -218,6 +235,15 @@ def __repr__(self) -> str: return "".format(self.asset_href) +class ItemAssetsSatExtension(SatExtension[item_assets.AssetDefinition]): + properties: Dict[str, Any] + asset_defn: item_assets.AssetDefinition + + def __init__(self, item_asset: item_assets.AssetDefinition): + self.asset_defn = item_asset + self.properties = item_asset.properties + + class SummariesSatExtension(SummariesExtension): """A concrete implementation of :class:`~SummariesExtension` that extends the ``summaries`` field of a :class:`~pystac.Collection` to include properties diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index 236f7e2c3..ec576f9dc 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -8,7 +8,7 @@ from __future__ import annotations import copy -from typing import Any, Dict, Generic, List, Optional, TypeVar, Union, cast +from typing import Any, Dict, Generic, List, Literal, Optional, TypeVar, Union, cast from urllib import parse import pystac @@ -117,6 +117,7 @@ class ScientificExtension( >>> sci_ext = ScientificExtension.ext(item) """ + name: Literal["sci"] = "sci" obj: pystac.STACObject def __init__(self, obj: pystac.STACObject) -> None: diff --git a/pystac/extensions/storage.py b/pystac/extensions/storage.py index 0d8bc94c9..4e5345efd 100644 --- a/pystac/extensions/storage.py +++ b/pystac/extensions/storage.py @@ -11,6 +11,7 @@ Generic, Iterable, List, + Literal, Optional, Set, TypeVar, @@ -19,6 +20,7 @@ ) import pystac +from pystac.extensions import item_assets from pystac.extensions.base import ( ExtensionManagementMixin, PropertiesExtension, @@ -27,7 +29,7 @@ from pystac.extensions.hooks import ExtensionHooks from pystac.utils import StringEnum -T = TypeVar("T", pystac.Item, pystac.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition) SCHEMA_URI: str = "https://stac-extensions.github.io/storage/v1.0.0/schema.json" PREFIX: str = "storage:" @@ -69,6 +71,8 @@ class StorageExtension( >>> storage_ext = StorageExtension.ext(item) """ + name: Literal["storage"] = "storage" + def apply( self, platform: Optional[CloudPlatform] = None, @@ -152,8 +156,11 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> StorageExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(StorageExtension[T], ItemStorageExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(StorageExtension[T], AssetStorageExtension(obj)) + elif isinstance(obj, item_assets.AssetDefinition): + cls.ensure_owner_has_extension(obj, add_if_missing) + return cast(StorageExtension[T], ItemAssetsStorageExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) @@ -218,6 +225,15 @@ def __repr__(self) -> str: return "".format(self.asset_href) +class ItemAssetsStorageExtension(StorageExtension[item_assets.AssetDefinition]): + properties: Dict[str, Any] + asset_defn: item_assets.AssetDefinition + + def __init__(self, item_asset: item_assets.AssetDefinition): + self.asset_defn = item_asset + self.properties = item_asset.properties + + class SummariesStorageExtension(SummariesExtension): """A concrete implementation of :class:`~SummariesExtension` that extends the ``summaries`` field of a :class:`~pystac.Collection` to include properties diff --git a/pystac/extensions/table.py b/pystac/extensions/table.py index ed5c9ccc0..c99a56f99 100644 --- a/pystac/extensions/table.py +++ b/pystac/extensions/table.py @@ -2,14 +2,17 @@ from __future__ import annotations -from typing import Any, Dict, Generic, List, Optional, TypeVar, Union, cast +from typing import Any, Dict, Generic, List, Literal, Optional, TypeVar, Union, cast import pystac +from pystac.extensions import item_assets from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension from pystac.extensions.hooks import ExtensionHooks from pystac.utils import get_required -T = TypeVar("T", pystac.Collection, pystac.Item, pystac.Asset) +T = TypeVar( + "T", pystac.Collection, pystac.Item, pystac.Asset, item_assets.AssetDefinition +) SCHEMA_URI = "https://stac-extensions.github.io/table/v1.2.0/schema.json" @@ -136,6 +139,8 @@ class TableExtension( """ + name: Literal["table"] = "table" + @classmethod def get_schema_uri(cls) -> str: return SCHEMA_URI @@ -158,8 +163,11 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> TableExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(TableExtension[T], ItemTableExtension(obj)) if isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(TableExtension[T], AssetTableExtension(obj)) + elif isinstance(obj, item_assets.AssetDefinition): + cls.ensure_owner_has_extension(obj, add_if_missing) + return cast(TableExtension[T], ItemAssetsTableExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) @@ -286,6 +294,15 @@ def __repr__(self) -> str: return "".format(self.asset_href) +class ItemAssetsTableExtension(TableExtension[item_assets.AssetDefinition]): + properties: Dict[str, Any] + asset_defn: item_assets.AssetDefinition + + def __init__(self, item_asset: item_assets.AssetDefinition): + self.asset_defn = item_asset + self.properties = item_asset.properties + + class TableExtensinoHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI prev_extension_ids = { diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index 3b4779627..b84943f73 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -3,7 +3,7 @@ from __future__ import annotations from datetime import datetime -from typing import Any, Dict, Generic, Iterable, Optional, TypeVar, Union, cast +from typing import Any, Dict, Generic, Iterable, Literal, Optional, TypeVar, Union, cast import pystac from pystac.extensions.base import ( @@ -43,6 +43,8 @@ class TimestampsExtension( >>> ts_ext = TimestampsExtension.ext(item) """ + name: Literal["timestamps"] = "timestamps" + def apply( self, published: Optional[datetime] = None, @@ -131,7 +133,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> TimestampsExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(TimestampsExtension[T], ItemTimestampsExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(TimestampsExtension[T], AssetTimestampsExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index 4ec636462..1bb6729f4 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -3,7 +3,18 @@ import warnings from contextlib import contextmanager -from typing import Any, Dict, Generator, Generic, List, Optional, TypeVar, Union, cast +from typing import ( + Any, + Dict, + Generator, + Generic, + List, + Literal, + Optional, + TypeVar, + Union, + cast, +) import pystac from pystac.errors import DeprecatedWarning @@ -60,6 +71,7 @@ class VersionExtension( >>> version_ext = VersionExtension.ext(item) """ + name: Literal["version"] = "version" obj: pystac.STACObject def __init__(self, obj: pystac.STACObject) -> None: diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index 7d25c401f..139008506 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import Any, Dict, Generic, Iterable, Optional, TypeVar, Union, cast +from typing import Any, Dict, Generic, Iterable, Literal, Optional, TypeVar, Union, cast import pystac +from pystac.extensions import item_assets from pystac.extensions.base import ( ExtensionManagementMixin, PropertiesExtension, @@ -13,7 +14,7 @@ from pystac.extensions.hooks import ExtensionHooks from pystac.summaries import RangeSummary -T = TypeVar("T", pystac.Item, pystac.Asset) +T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition) SCHEMA_URI: str = "https://stac-extensions.github.io/view/v1.0.0/schema.json" PREFIX: str = "view:" @@ -44,6 +45,8 @@ class ViewExtension( >>> view_ext = ViewExtension.ext(item) """ + name: Literal["view"] = "view" + def apply( self, off_nadir: Optional[float] = None, @@ -160,8 +163,11 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ViewExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return cast(ViewExtension[T], ItemViewExtension(obj)) elif isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(ViewExtension[T], AssetViewExtension(obj)) + elif isinstance(obj, item_assets.AssetDefinition): + cls.ensure_owner_has_extension(obj, add_if_missing) + return cast(ViewExtension[T], ItemAssetsViewExtension(obj)) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) @@ -226,6 +232,15 @@ def __repr__(self) -> str: return "".format(self.asset_href) +class ItemAssetsViewExtension(ViewExtension[item_assets.AssetDefinition]): + properties: Dict[str, Any] + asset_defn: item_assets.AssetDefinition + + def __init__(self, item_asset: item_assets.AssetDefinition): + self.asset_defn = item_asset + self.properties = item_asset.properties + + class SummariesViewExtension(SummariesExtension): """A concrete implementation of :class:`~SummariesExtension` that extends the ``summaries`` field of a :class:`~pystac.Collection` to include properties diff --git a/pystac/extensions/xarray_assets.py b/pystac/extensions/xarray_assets.py index b41776110..346c66637 100644 --- a/pystac/extensions/xarray_assets.py +++ b/pystac/extensions/xarray_assets.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any, Dict, Generic, List, Optional, TypeVar, Union +from typing import Any, Dict, Generic, List, Literal, Optional, TypeVar, Union import pystac from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension @@ -38,6 +38,8 @@ class XarrayAssetsExtension( """ + name: Literal["xarray"] = "xarray" + @classmethod def get_schema_uri(cls) -> str: return SCHEMA_URI @@ -60,7 +62,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> XarrayAssetsExtension[T]: cls.ensure_has_extension(obj, add_if_missing) return ItemXarrayAssetsExtension(obj) if isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return AssetXarrayAssetsExtension(obj) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) diff --git a/pystac/item.py b/pystac/item.py index 4ef676b7f..a8ff5de09 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -31,6 +31,8 @@ # avoids conflicts since there are also kwargs and attrs called `datetime` from datetime import datetime as Datetime + from pystac.extensions.ext import ItemExt + class Item(STACObject): """An Item is the core granular entity in a STAC, containing the core metadata @@ -575,3 +577,15 @@ def __geo_interface__(self) -> Dict[str, Any]: Dict[str, Any]: This item as a dictionary. """ return self.to_dict(include_self_link=False, transform_hrefs=False) + + @property + def ext(self) -> ItemExt: + """Accessor for extension classes on this item + + Example:: + + item.ext.proj.epsg = 4326 + """ + from pystac.extensions.ext import ItemExt + + return ItemExt(stac_object=self) diff --git a/pystac/utils.py b/pystac/utils.py index 9a1fdf9ce..f7063093e 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -86,6 +86,9 @@ class StringEnum(str, Enum): """Base :class:`enum.Enum` class for string enums that will serialize as the string value.""" + def __repr__(self) -> str: + return str(self.value) + def __str__(self) -> str: return cast(str, self.value) diff --git a/tests/conftest.py b/tests/conftest.py index 12470792b..3848134f9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,6 +29,12 @@ def item() -> Item: return Item("test-item", ARBITRARY_GEOM, ARBITRARY_BBOX, datetime.now(), {}) +@pytest.fixture +def asset(item: Item) -> Asset: + item.add_asset("foo", Asset("https://example.tif")) + return item.assets["foo"] + + @pytest.fixture def test_case_1_catalog() -> Catalog: return TestCases.case_1() diff --git a/tests/data-files/classification/collection-item-assets-raster-bands.json b/tests/data-files/classification/collection-item-assets-raster-bands.json index 5503c73e7..16776ad08 100644 --- a/tests/data-files/classification/collection-item-assets-raster-bands.json +++ b/tests/data-files/classification/collection-item-assets-raster-bands.json @@ -112,9 +112,9 @@ }, "license": "proprietary", "stac_extensions": [ - "https://stac-extensions.github.io/projection/v1.0.0/schema.json", - "https://stac-extensions.github.io/eo/v1.0.0/schema.json", - "https://stac-extensions.github.io/raster/v1.0.0/schema.json", - "https://stac-extensions.github.io/classification/v1.0.0/schema.json" + "https://stac-extensions.github.io/projection/v1.1.0/schema.json", + "https://stac-extensions.github.io/eo/v1.1.0/schema.json", + "https://stac-extensions.github.io/raster/v1.1.0/schema.json", + "https://stac-extensions.github.io/classification/v1.1.0/schema.json" ] } \ No newline at end of file diff --git a/tests/extensions/test_classification.py b/tests/extensions/test_classification.py index 1a1439d27..236265063 100644 --- a/tests/extensions/test_classification.py +++ b/tests/extensions/test_classification.py @@ -308,6 +308,8 @@ def test_item_assets_extension(collection: Collection) -> None: ] ext = ClassificationExtension.ext(item_asset) ext.__repr__() + assert ClassificationExtension.get_schema_uri() in collection.stac_extensions + assert collection.ext.item_assets["cloud-mask-raster"].ext.has("classification") def test_older_extension_version(landsat_item: Item) -> None: diff --git a/tests/extensions/test_custom.py b/tests/extensions/test_custom.py index 764173f48..777afbb19 100644 --- a/tests/extensions/test_custom.py +++ b/tests/extensions/test_custom.py @@ -54,7 +54,7 @@ def get_schema_uri(cls) -> str: @classmethod def ext(cls, obj: T, add_if_missing: bool = False) -> "CustomExtension[T]": if isinstance(obj, pystac.Asset): - cls.validate_owner_has_extension(obj, add_if_missing) + cls.ensure_owner_has_extension(obj, add_if_missing) return cast(CustomExtension[T], AssetCustomExtension(obj)) if isinstance(obj, pystac.Item): cls.ensure_has_extension(obj, add_if_missing) diff --git a/tests/extensions/test_eo.py b/tests/extensions/test_eo.py index dc5b5616f..02f5fb00d 100644 --- a/tests/extensions/test_eo.py +++ b/tests/extensions/test_eo.py @@ -6,6 +6,7 @@ import pystac from pystac import ExtensionTypeError, Item +from pystac.errors import ExtensionNotImplemented from pystac.extensions.eo import PREFIX, SNOW_COVER_PROP, Band, EOExtension from pystac.extensions.projection import ProjectionExtension from pystac.summaries import RangeSummary @@ -234,14 +235,12 @@ def test_summaries(self) -> None: def test_summaries_adds_uri(self) -> None: col = pystac.Collection.from_file(self.EO_COLLECTION_URI) col.stac_extensions = [] - self.assertRaisesRegex( - pystac.ExtensionNotImplemented, - r"Could not find extension schema URI.*", - EOExtension.summaries, - col, - False, - ) - _ = EOExtension.summaries(col, add_if_missing=True) + with pytest.raises( + pystac.ExtensionNotImplemented, match="Extension 'eo' is not implemented" + ): + EOExtension.summaries(col, add_if_missing=False) + + EOExtension.summaries(col, add_if_missing=True) self.assertIn(EOExtension.get_schema_uri(), col.stac_extensions) @@ -487,3 +486,22 @@ def test_exception_should_include_hint_if_obj_is_collection( match="Hint: Did you mean to use `EOExtension.summaries` instead?", ): EOExtension.ext(collection) # type:ignore + + +def test_ext_syntax(ext_item: pystac.Item) -> None: + assert ext_item.ext.eo.cloud_cover == 78 + assert (bands := ext_item.assets["B1"].ext.eo.bands) + assert bands[0].name == "B1" + + +def test_ext_syntax_remove(ext_item: pystac.Item) -> None: + ext_item.ext.remove("eo") + assert ext_item.ext.has("eo") is False + with pytest.raises(ExtensionNotImplemented): + ext_item.ext.eo + + +def test_ext_syntax_add(item: pystac.Item) -> None: + item.ext.add("eo") + assert item.ext.has("eo") is True + assert isinstance(item.ext.eo, EOExtension) diff --git a/tests/extensions/test_ext.py b/tests/extensions/test_ext.py new file mode 100644 index 000000000..a4b89ac1d --- /dev/null +++ b/tests/extensions/test_ext.py @@ -0,0 +1,123 @@ +import logging + +import pytest + +import pystac +from pystac.errors import ExtensionNotImplemented +from pystac.extensions.ext import ( + EXTENSION_NAME_MAPPING, + EXTENSION_NAMES, + AssetExt, + CollectionExt, + ItemExt, +) +from tests.conftest import get_data_file + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger() + + +@pytest.fixture +def eo_ext_item() -> pystac.Item: + ext_item_uri = get_data_file("eo/eo-landsat-example.json") + return pystac.Item.from_file(ext_item_uri) + + +def test_ext_syntax_has(eo_ext_item: pystac.Item) -> None: + assert eo_ext_item.ext.has("eo") is True + assert eo_ext_item.ext.has("proj") is False + + assert eo_ext_item.assets["B1"].ext.has("eo") is True + assert eo_ext_item.assets["B1"].ext.has("proj") is False + + +def test_ext_syntax_raises_if_ext_not_on_obj(eo_ext_item: pystac.Item) -> None: + with pytest.raises(ExtensionNotImplemented): + eo_ext_item.ext.proj.epsg + + +def test_ext_syntax_ext_can_be_added(eo_ext_item: pystac.Item) -> None: + eo_ext_item.ext.add("proj") + assert eo_ext_item.ext.proj.epsg is None + + +def test_ext_syntax_trying_to_add_invalid_ext_raises(item: pystac.Item) -> None: + with pytest.raises(KeyError, match="Extension 'foo' is not a valid extension"): + item.ext.add("foo") # type: ignore + + +def test_ext_syntax_ext_can_be_removed(eo_ext_item: pystac.Item) -> None: + original_n = len(eo_ext_item.stac_extensions) + eo_ext_item.ext.remove("eo") + with pytest.raises( + ExtensionNotImplemented, match="Extension 'eo' is not implemented" + ): + eo_ext_item.ext.eo + assert len(eo_ext_item.stac_extensions) == original_n - 1 + + +all_asset_ext_props = {a for a in dir(AssetExt) if not a.startswith("_")} - { + "has", + "add", + "remove", +} +all_item_ext_props = {a for a in dir(ItemExt) if not a.startswith("_")} - { + "has", + "add", + "remove", +} +all_collection_ext_props = {a for a in dir(CollectionExt) if not a.startswith("_")} - { + "has", + "add", + "remove", +} + + +@pytest.mark.parametrize("name", all_asset_ext_props) +def test_ext_syntax_every_prop_can_be_added_to_asset( + asset: pystac.Asset, name: EXTENSION_NAMES +) -> None: + assert asset.ext.has(name) is False + asset.ext.add(name) + assert asset.ext.has(name) is True + asset.ext.remove(name) + with pytest.raises( + ExtensionNotImplemented, match=f"Extension '{name}' is not implemented" + ): + getattr(asset.ext, name) + + +@pytest.mark.parametrize("name", all_item_ext_props) +def test_ext_syntax_every_prop_can_be_added_to_item( + item: pystac.Item, name: EXTENSION_NAMES +) -> None: + assert item.ext.has(name) is False + item.ext.add(name) + assert item.ext.has(name) is True + item.ext.remove(name) + with pytest.raises( + ExtensionNotImplemented, match=f"Extension '{name}' is not implemented" + ): + getattr(item.ext, name) + + +@pytest.mark.parametrize("name", all_collection_ext_props) +def test_ext_syntax_every_prop_can_be_added_to_collection( + collection: pystac.Collection, name: EXTENSION_NAMES +) -> None: + assert collection.ext.has(name) is False + collection.ext.add(name) + assert collection.ext.has(name) is True + collection.ext.remove(name) + with pytest.raises( + ExtensionNotImplemented, match=f"Extension '{name}' is not implemented" + ): + getattr(collection.ext, name) + + +def test_ext_syntax_every_name_has_a_prop() -> None: + assert { + *all_asset_ext_props, + *all_item_ext_props, + *all_collection_ext_props, + } == set(EXTENSION_NAME_MAPPING.keys()) diff --git a/tests/extensions/test_item_assets.py b/tests/extensions/test_item_assets.py index d02a7c8af..a847975b0 100644 --- a/tests/extensions/test_item_assets.py +++ b/tests/extensions/test_item_assets.py @@ -109,3 +109,10 @@ def test_extra_fields(collection: Collection) -> None: assert collection_as_dict["item_assets"]["data"]["raster:bands"] == [{"nodata": 42}] asset = asset_definition.create_asset("asset.tif") assert asset.extra_fields["raster:bands"] == [{"nodata": 42}] + + collection.ext.item_assets["data"].ext.add("raster") + assert (bands := collection.ext.item_assets["data"].ext.raster.bands) + assert bands[0].nodata == 42 + + assert collection.ext.item_assets["data"].ext.has("raster") + assert collection.ext.has("raster") diff --git a/tests/extensions/test_label.py b/tests/extensions/test_label.py index 256f1d0e7..73f4b2e42 100644 --- a/tests/extensions/test_label.py +++ b/tests/extensions/test_label.py @@ -472,14 +472,13 @@ class LabelSummariesTest(unittest.TestCase): def test_summaries_adds_uri(self) -> None: col = pystac.Collection.from_file(self.EXAMPLE_COLLECTION) col.stac_extensions = [] - self.assertRaisesRegex( + with pytest.raises( pystac.ExtensionNotImplemented, - r"Could not find extension schema URI.*", - LabelExtension.summaries, - col, - False, - ) - _ = LabelExtension.summaries(col, True) + match="Extension 'label' is not implemented", + ): + LabelExtension.summaries(col, add_if_missing=False) + + LabelExtension.summaries(col, True) self.assertIn(LabelExtension.get_schema_uri(), col.stac_extensions) diff --git a/tests/extensions/test_projection.py b/tests/extensions/test_projection.py index 463f077eb..32b6dc67c 100644 --- a/tests/extensions/test_projection.py +++ b/tests/extensions/test_projection.py @@ -7,6 +7,7 @@ import pystac from pystac import ExtensionTypeError, Item +from pystac.errors import ExtensionNotImplemented from pystac.extensions.projection import ProjectionExtension from pystac.utils import get_opt from tests.utils import TestCases, assert_to_from_dict @@ -535,14 +536,12 @@ def test_set_summaries(self) -> None: def test_summaries_adds_uri(self) -> None: col = pystac.Collection.from_file(self.example_uri) col.stac_extensions = [] - self.assertRaisesRegex( - pystac.ExtensionNotImplemented, - r"Could not find extension schema URI.*", - ProjectionExtension.summaries, - col, - False, - ) - _ = ProjectionExtension.summaries(col, True) + with pytest.raises( + pystac.ExtensionNotImplemented, match="Extension 'proj' is not implemented" + ): + ProjectionExtension.summaries(col, add_if_missing=False) + + ProjectionExtension.summaries(col, True) self.assertIn(ProjectionExtension.get_schema_uri(), col.stac_extensions) @@ -588,3 +587,21 @@ def test_newer_extension_version(projection_landsat8_item: Item) -> None: migrated_item = pystac.Item.from_dict(item_as_dict, migrate=True) assert ProjectionExtension.has_extension(migrated_item) assert new in migrated_item.stac_extensions + + +def test_ext_syntax(projection_landsat8_item: pystac.Item) -> None: + ext_item = projection_landsat8_item + assert ext_item.ext.proj.epsg == 32614 + assert ext_item.assets["B1"].ext.proj.epsg == 32614 + + +def test_ext_syntax_remove(projection_landsat8_item: pystac.Item) -> None: + ext_item = projection_landsat8_item + ext_item.ext.remove("proj") + with pytest.raises(ExtensionNotImplemented): + ext_item.ext.proj + + +def test_ext_syntax_add(item: pystac.Item) -> None: + item.ext.add("proj") + assert isinstance(item.ext.proj, ProjectionExtension) diff --git a/tests/extensions/test_raster.py b/tests/extensions/test_raster.py index 5b60539b3..83b1d82f9 100644 --- a/tests/extensions/test_raster.py +++ b/tests/extensions/test_raster.py @@ -266,14 +266,13 @@ def test_summaries_adds_uri(self) -> None: TestCases.get_path("data-files/label/spacenet-roads/roads_collection.json") ) col.stac_extensions = [] - self.assertRaisesRegex( + with pytest.raises( pystac.ExtensionNotImplemented, - r"Could not find extension schema URI.*", - RasterExtension.summaries, - col, - False, - ) - _ = RasterExtension.summaries(col, True) + match="Extension 'raster' is not implemented", + ): + RasterExtension.summaries(col, add_if_missing=False) + + RasterExtension.summaries(col, True) self.assertIn(RasterExtension.get_schema_uri(), col.stac_extensions) diff --git a/tests/extensions/test_sat.py b/tests/extensions/test_sat.py index db615f31b..44dd8151e 100644 --- a/tests/extensions/test_sat.py +++ b/tests/extensions/test_sat.py @@ -346,13 +346,12 @@ def test_anx_datetime(self) -> None: def test_summaries_adds_uri(self) -> None: col = self.collection() col.stac_extensions = [] - self.assertRaisesRegex( + with pytest.raises( pystac.ExtensionNotImplemented, - r"Could not find extension schema URI.*", - SatExtension.summaries, - col, - False, - ) + match="Extension 'sat' is not implemented", + ): + SatExtension.summaries(col, add_if_missing=False) + _ = SatExtension.summaries(col, True) self.assertIn(SatExtension.get_schema_uri(), col.stac_extensions) diff --git a/tests/extensions/test_scientific.py b/tests/extensions/test_scientific.py index 76ae95c90..4c64d670e 100644 --- a/tests/extensions/test_scientific.py +++ b/tests/extensions/test_scientific.py @@ -481,13 +481,12 @@ def test_set_doi_summaries(self) -> None: def test_summaries_adds_uri(self) -> None: collection = self.collection.clone() collection.stac_extensions = [] - self.assertRaisesRegex( + with pytest.raises( pystac.ExtensionNotImplemented, - r"Could not find extension schema URI.*", - ScientificExtension.summaries, - collection, - False, - ) + match="Extension 'sci' is not implemented", + ): + ScientificExtension.summaries(collection, add_if_missing=False) + _ = ScientificExtension.summaries(collection, True) self.assertIn(ScientificExtension.get_schema_uri(), collection.stac_extensions) diff --git a/tests/extensions/test_storage.py b/tests/extensions/test_storage.py index 60616fc54..f7bbe5134 100644 --- a/tests/extensions/test_storage.py +++ b/tests/extensions/test_storage.py @@ -200,13 +200,12 @@ def test_tier(self) -> None: def test_summaries_adds_uri(self) -> None: col = self.naip_collection col.stac_extensions = [] - self.assertRaisesRegex( + with pytest.raises( pystac.ExtensionNotImplemented, - r"Could not find extension schema URI.*", - StorageExtension.summaries, - col, - False, - ) + match="Extension 'storage' is not implemented", + ): + StorageExtension.summaries(col, add_if_missing=False) + _ = StorageExtension.summaries(col, add_if_missing=True) self.assertIn(StorageExtension.get_schema_uri(), col.stac_extensions) diff --git a/tests/extensions/test_timestamps.py b/tests/extensions/test_timestamps.py index 12f5ff121..a3065c1c7 100644 --- a/tests/extensions/test_timestamps.py +++ b/tests/extensions/test_timestamps.py @@ -347,13 +347,12 @@ def test_unpublished(self) -> None: def test_summaries_adds_uri(self) -> None: collection = self.collection() collection.stac_extensions = [] - self.assertRaisesRegex( + with pytest.raises( pystac.ExtensionNotImplemented, - r"Could not find extension schema URI.*", - TimestampsExtension.summaries, - collection, - False, - ) + match="Extension 'timestamps' is not implemented", + ): + TimestampsExtension.summaries(collection, add_if_missing=False) + _ = TimestampsExtension.summaries(collection, True) self.assertIn(TimestampsExtension.get_schema_uri(), collection.stac_extensions) diff --git a/tests/extensions/test_view.py b/tests/extensions/test_view.py index 84573b69f..f5725d0af 100644 --- a/tests/extensions/test_view.py +++ b/tests/extensions/test_view.py @@ -368,13 +368,12 @@ def test_set_sun_elevation_summaries(self) -> None: def test_summaries_adds_uri(self) -> None: collection = self.collection.clone() collection.stac_extensions = [] - self.assertRaisesRegex( + with pytest.raises( pystac.ExtensionNotImplemented, - r"Could not find extension schema URI.*", - ViewExtension.summaries, - collection, - False, - ) + match="Extension 'view' is not implemented", + ): + ViewExtension.summaries(collection, add_if_missing=False) + _ = ViewExtension.summaries(collection, True) self.assertIn(ViewExtension.get_schema_uri(), collection.stac_extensions)