Skip to content

Commit 7675754

Browse files
committed
Merge branch 'main' into must-include-scheme
2 parents 8446839 + ab747be commit 7675754

File tree

68 files changed

+1056
-507
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+1056
-507
lines changed

Diff for: .github/workflows/continuous-integration.yml

+7-11
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,12 @@ jobs:
3232
- ubuntu-latest
3333
- windows-latest
3434
- macos-latest
35-
exclude:
36-
# https://github.com/stac-utils/pystac/issues/1470
37-
- os: windows-latest
38-
python-version: "3.13"
3935
steps:
4036
- uses: actions/checkout@v4
4137
- uses: actions/setup-python@v5
4238
with:
4339
python-version: ${{ matrix.python-version }}
44-
- uses: astral-sh/setup-uv@v3
40+
- uses: astral-sh/setup-uv@v5
4541
with:
4642
enable-cache: true
4743
- name: Sync
@@ -64,7 +60,7 @@ jobs:
6460
- uses: actions/setup-python@v5
6561
with:
6662
python-version: "3.10"
67-
- uses: astral-sh/setup-uv@v3
63+
- uses: astral-sh/setup-uv@v5
6864
with:
6965
enable-cache: true
7066
- name: Install with dependencies
@@ -80,7 +76,7 @@ jobs:
8076
# will trigger a failure for coverage drops in a later job
8177
run: uv run coverage xml --fail-under 0
8278
- name: Upload All coverage to Codecov
83-
uses: codecov/codecov-action@v4
79+
uses: codecov/codecov-action@v5
8480
if: ${{ env.GITHUB_REPOSITORY }} == 'stac-utils/pystac'
8581
with:
8682
token: ${{ secrets.CODECOV_TOKEN }}
@@ -105,7 +101,7 @@ jobs:
105101
- uses: actions/setup-python@v5
106102
with:
107103
python-version: ${{ matrix.python-version }}
108-
- uses: astral-sh/setup-uv@v3
104+
- uses: astral-sh/setup-uv@v5
109105
with:
110106
enable-cache: true
111107
- name: Sync
@@ -120,7 +116,7 @@ jobs:
120116
- uses: actions/setup-python@v5
121117
with:
122118
python-version: "3.10"
123-
- uses: astral-sh/setup-uv@v3
119+
- uses: astral-sh/setup-uv@v5
124120
with:
125121
enable-cache: true
126122
- name: Sync
@@ -140,7 +136,7 @@ jobs:
140136
- uses: actions/setup-python@v5
141137
with:
142138
python-version: "3.10"
143-
- uses: astral-sh/setup-uv@v3
139+
- uses: astral-sh/setup-uv@v5
144140
with:
145141
enable-cache: true
146142
- name: Sync
@@ -157,7 +153,7 @@ jobs:
157153
- uses: actions/setup-python@v5
158154
with:
159155
python-version: "3.10"
160-
- uses: astral-sh/setup-uv@v3
156+
- uses: astral-sh/setup-uv@v5
161157
with:
162158
enable-cache: true
163159
- name: Install pandoc

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ stdout*
88
/integration*
99
.idea
1010
.vscode
11+
.actrc
1112

1213

1314
# Sphinx documentation

Diff for: CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@
22

33
## [Unreleased]
44

5+
6+
### Added
7+
8+
- Top-level `item_assets` dict on `Collection`s ([#1476](https://github.com/stac-utils/pystac/pull/1476))
9+
510
### Changed
611

712
- Write STAC v1.1.0 ([#1427](https://github.com/stac-utils/pystac/pull/1427))
813
- Use [uv](https://github.com/astral-sh/uv) for development dependencies and docs ([#1439](https://github.com/stac-utils/pystac/pull/1439))
14+
- Correctly detect absolute file path ref on windows, reflecting change in python 3.13 ([#1475](https://github.com/stac-utils/pystac/pull/14750)) (only effects python 3.13)
15+
- Deprecated `ItemAssetExtension` ([#1476](https://github.com/stac-utils/pystac/pull/1476))
916

1017
## [v1.11.0] - 2024-09-26
1118

Diff for: docs/api/item_assets.rst

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pystac.item_assets
2+
==================
3+
4+
.. automodule:: pystac.item_assets
5+
:members:
6+
:undoc-members:
7+
:noindex:

Diff for: docs/api/pystac.rst

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pystac
1515
Summaries
1616
Item
1717
Asset
18+
ItemAssetDefinition
1819
CommonMetadata
1920
ItemCollection
2021
Link
@@ -116,6 +117,14 @@ Asset
116117
:members:
117118
:undoc-members:
118119

120+
ItemAssetDefinition
121+
-------------------
122+
123+
.. autoclass:: pystac.ItemAssetDefinition
124+
:members:
125+
:undoc-members:
126+
127+
119128
CommonMetadata
120129
--------------
121130

Diff for: docs/tutorials/creating-a-landsat-stac.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,7 @@
11191119
"cell_type": "markdown",
11201120
"metadata": {},
11211121
"source": [
1122-
"Several QA bands are provided that utilize bit-wise masks which we can define using the [`classification` extension](https://github.com/stac-extensions/classification). Because these definitions can be verbose, we provide some additional helper functions to minimize the length of their definition."
1122+
"Several QA bands are provided that utilize bit-wise masks which we can define using the [classification extension](https://github.com/stac-extensions/classification). Because these definitions can be verbose, we provide some additional helper functions to minimize the length of their definition."
11231123
]
11241124
},
11251125
{

Diff for: pystac/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"RangeSummary",
3434
"Item",
3535
"Asset",
36+
"ItemAssetDefinition",
3637
"ItemCollection",
3738
"Provider",
3839
"ProviderRole",
@@ -81,6 +82,7 @@
8182
from pystac.summaries import RangeSummary, Summaries
8283
from pystac.asset import Asset
8384
from pystac.item import Item
85+
from pystac.item_assets import ItemAssetDefinition
8486
from pystac.item_collection import ItemCollection
8587
from pystac.provider import ProviderRole, Provider
8688
from pystac.utils import HREF

Diff for: pystac/collection.py

+58
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from pystac.asset import Asset, Assets
2121
from pystac.catalog import Catalog
2222
from pystac.errors import DeprecatedWarning, ExtensionNotImplemented, STACTypeError
23+
from pystac.item_assets import ItemAssetDefinition, _ItemAssets
2324
from pystac.layout import HrefLayoutStrategy
2425
from pystac.link import Link
2526
from pystac.provider import Provider
@@ -553,6 +554,7 @@ def __init__(
553554
self.keywords = keywords
554555
self.providers = providers
555556
self.summaries = summaries or Summaries.empty()
557+
self._item_assets: _ItemAssets | None = None
556558

557559
self.assets = {}
558560
if assets is not None:
@@ -731,6 +733,62 @@ def get_item(self, id: str, recursive: bool = False) -> Item | None:
731733
return super().get_item(id, recursive=recursive)
732734
raise e
733735

736+
@property
737+
def item_assets(self) -> dict[str, ItemAssetDefinition]:
738+
"""Accessor for `item_assets
739+
<https://github.com/radiantearth/stac-spec/blob/v1.1.0/collection-spec/collection-spec.md#item_assets>`__
740+
on this collection.
741+
742+
Example::
743+
744+
.. code-block:: python
745+
746+
>>> print(collection.item_assets)
747+
{'thumbnail': <pystac.item_assets.ItemAssetDefinition at 0x72aea0420750>,
748+
'metadata': <pystac.item_assets.ItemAssetDefinition at 0x72aea017dc90>,
749+
'B5': <pystac.item_assets.ItemAssetDefinition at 0x72aea017efd0>,
750+
'B6': <pystac.item_assets.ItemAssetDefinition at 0x72aea016d5d0>,
751+
'B7': <pystac.item_assets.ItemAssetDefinition at 0x72aea016e050>,
752+
'B8': <pystac.item_assets.ItemAssetDefinition at 0x72aea016da90>}
753+
>>> collection.item_assets["thumbnail"].title
754+
'Thumbnail'
755+
756+
Set attributes on :class:`~pystac.ItemAssetDefinition` objects
757+
758+
.. code-block:: python
759+
760+
>>> collection.item_assets["thumbnail"].title = "New Title"
761+
762+
Add to the ``item_assets`` dict:
763+
764+
.. code-block:: python
765+
766+
>>> collection.item_assets["B4"] = {
767+
'type': 'image/tiff; application=geotiff; profile=cloud-optimized',
768+
'eo:bands': [{'name': 'B4', 'common_name': 'red'}]
769+
}
770+
>>> collection.item_assets["B4"].owner == collection
771+
True
772+
"""
773+
if self._item_assets is None:
774+
self._item_assets = _ItemAssets(self)
775+
return self._item_assets
776+
777+
@item_assets.setter
778+
def item_assets(
779+
self, item_assets: dict[str, ItemAssetDefinition | dict[str, Any]] | None
780+
) -> None:
781+
# clear out the cached value
782+
self._item_assets = None
783+
784+
if item_assets is None:
785+
self.extra_fields.pop("item_assets")
786+
else:
787+
self.extra_fields["item_assets"] = {
788+
k: v if isinstance(v, dict) else v.to_dict()
789+
for k, v in item_assets.items()
790+
}
791+
734792
def update_extent_from_items(self) -> None:
735793
"""
736794
Update datetime and bbox based on all items to a single bbox and time window.

Diff for: pystac/extensions/base.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from abc import ABC, abstractmethod
66
from collections.abc import Iterable
77
from typing import (
8-
TYPE_CHECKING,
98
Any,
109
Generic,
1110
TypeVar,
@@ -14,9 +13,6 @@
1413

1514
import pystac
1615

17-
if TYPE_CHECKING:
18-
from pystac.extensions.item_assets import AssetDefinition
19-
2016
VERSION_REGEX = re.compile("/v[0-9].[0-9].*/")
2117

2218

@@ -158,7 +154,7 @@ def has_extension(cls, obj: S) -> bool:
158154
@classmethod
159155
def validate_owner_has_extension(
160156
cls,
161-
asset: pystac.Asset | AssetDefinition,
157+
asset: pystac.Asset | pystac.ItemAssetDefinition,
162158
add_if_missing: bool = False,
163159
) -> None:
164160
"""
@@ -190,7 +186,7 @@ def validate_owner_has_extension(
190186
@classmethod
191187
def ensure_owner_has_extension(
192188
cls,
193-
asset_or_link: pystac.Asset | AssetDefinition | pystac.Link,
189+
asset_or_link: pystac.Asset | pystac.ItemAssetDefinition | pystac.Link,
194190
add_if_missing: bool = False,
195191
) -> None:
196192
"""Given an :class:`~pystac.Asset`, checks if the asset's owner has this

Diff for: pystac/extensions/classification.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
)
1717

1818
import pystac
19-
from pystac.extensions import item_assets
2019
from pystac.extensions.base import (
2120
ExtensionManagementMixin,
2221
PropertiesExtension,
@@ -27,7 +26,7 @@
2726
from pystac.serialization.identify import STACJSONDescription, STACVersionID
2827
from pystac.utils import get_required, map_opt
2928

30-
T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition, RasterBand)
29+
T = TypeVar("T", pystac.Item, pystac.Asset, pystac.ItemAssetDefinition, RasterBand)
3130

3231
SCHEMA_URI_PATTERN: str = (
3332
"https://stac-extensions.github.io/classification/v{version}/schema.json"
@@ -492,7 +491,7 @@ class ClassificationExtension(
492491
"""An abstract class that can be used to extend the properties of
493492
:class:`~pystac.Item`, :class:`~pystac.Asset`,
494493
:class:`~pystac.extension.raster.RasterBand`, or
495-
:class:`~pystac.extension.item_assets.AssetDefinition` with properties from the
494+
:class:`~pystac.ItemAssetDefinition` with properties from the
496495
:stac-ext:`Classification Extension <classification>`. This class is generic
497496
over the type of STAC object being extended.
498497
@@ -525,8 +524,10 @@ def apply(
525524
or bitfields is None
526525
and classes is not None
527526
), "Must set exactly one of `classes` or `bitfields`"
528-
self.classes = classes
529-
self.bitfields = bitfields
527+
if classes:
528+
self.classes = classes
529+
if bitfields:
530+
self.bitfields = bitfields
530531

531532
@property
532533
def classes(self) -> list[Classification] | None:
@@ -600,7 +601,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ClassificationExtension[T]
600601
601602
This extension can be applied to instances of :class:`~pystac.Item`,
602603
:class:`~pystac.Asset`,
603-
:class:`~pystac.extensions.item_assets.AssetDefinition`, or
604+
:class:`~pystac.ItemAssetDefinition`, or
604605
:class:`~pystac.extension.raster.RasterBand`.
605606
606607
Raises:
@@ -612,7 +613,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> ClassificationExtension[T]
612613
elif isinstance(obj, pystac.Asset):
613614
cls.ensure_owner_has_extension(obj, add_if_missing)
614615
return cast(ClassificationExtension[T], AssetClassificationExtension(obj))
615-
elif isinstance(obj, item_assets.AssetDefinition):
616+
elif isinstance(obj, pystac.ItemAssetDefinition):
616617
cls.ensure_owner_has_extension(obj, add_if_missing)
617618
return cast(
618619
ClassificationExtension[T], ItemAssetsClassificationExtension(obj)
@@ -663,17 +664,19 @@ def __repr__(self) -> str:
663664

664665

665666
class ItemAssetsClassificationExtension(
666-
ClassificationExtension[item_assets.AssetDefinition]
667+
ClassificationExtension[pystac.ItemAssetDefinition]
667668
):
668669
properties: dict[str, Any]
669-
asset_defn: item_assets.AssetDefinition
670+
asset_defn: pystac.ItemAssetDefinition
670671

671-
def __init__(self, item_asset: item_assets.AssetDefinition):
672+
def __init__(self, item_asset: pystac.ItemAssetDefinition):
672673
self.asset_defn = item_asset
673674
self.properties = item_asset.properties
674675

675676
def __repr__(self) -> str:
676-
return f"<ItemAssetsClassificationExtension AssetDefinition={self.asset_defn}"
677+
return (
678+
f"<ItemAssetsClassificationExtension ItemAssetDefinition={self.asset_defn}"
679+
)
677680

678681

679682
class RasterBandClassificationExtension(ClassificationExtension[RasterBand]):

Diff for: pystac/extensions/datacube.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66
from typing import Any, Generic, Literal, TypeVar, Union, cast
77

88
import pystac
9-
from pystac.extensions import item_assets
109
from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension
1110
from pystac.extensions.hooks import ExtensionHooks
1211
from pystac.utils import StringEnum, get_required, map_opt
1312

1413
T = TypeVar(
15-
"T", pystac.Collection, pystac.Item, pystac.Asset, item_assets.AssetDefinition
14+
"T", pystac.Collection, pystac.Item, pystac.Asset, pystac.ItemAssetDefinition
1615
)
1716

1817
SCHEMA_URI = "https://stac-extensions.github.io/datacube/v2.2.0/schema.json"
@@ -619,7 +618,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> DatacubeExtension[T]:
619618
elif isinstance(obj, pystac.Asset):
620619
cls.ensure_owner_has_extension(obj, add_if_missing)
621620
return cast(DatacubeExtension[T], AssetDatacubeExtension(obj))
622-
elif isinstance(obj, item_assets.AssetDefinition):
621+
elif isinstance(obj, pystac.ItemAssetDefinition):
623622
cls.ensure_owner_has_extension(obj, add_if_missing)
624623
return cast(DatacubeExtension[T], ItemAssetsDatacubeExtension(obj))
625624
else:
@@ -691,11 +690,11 @@ def __repr__(self) -> str:
691690
return f"<AssetDatacubeExtension Item id={self.asset_href}>"
692691

693692

694-
class ItemAssetsDatacubeExtension(DatacubeExtension[item_assets.AssetDefinition]):
693+
class ItemAssetsDatacubeExtension(DatacubeExtension[pystac.ItemAssetDefinition]):
695694
properties: dict[str, Any]
696-
asset_defn: item_assets.AssetDefinition
695+
asset_defn: pystac.ItemAssetDefinition
697696

698-
def __init__(self, item_asset: item_assets.AssetDefinition):
697+
def __init__(self, item_asset: pystac.ItemAssetDefinition):
699698
self.asset_defn = item_asset
700699
self.properties = item_asset.properties
701700

0 commit comments

Comments
 (0)