Skip to content

Commit 3ce03ed

Browse files
authored
feat(v2): asset hrefs (#1526)
* feat(v2): asset hrefs * fix: typing
1 parent a039414 commit 3ce03ed

File tree

26 files changed

+1774
-351
lines changed

26 files changed

+1774
-351
lines changed

src/pystac/__init__.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
)
1414
from .extent import Extent, SpatialExtent, TemporalExtent
1515
from .functions import get_stac_version, read_dict, set_stac_version
16-
from .io import DefaultReader, DefaultWriter, read_file, write_file
16+
from .io import read_file, write_file
1717
from .item import Item
1818
from .link import Link
19-
from .render import DefaultRenderer, Renderer
19+
from .media_type import MediaType
2020
from .stac_object import STACObject
2121

2222

@@ -26,32 +26,31 @@ def __getattr__(name: str) -> Any:
2626
from .stac_io import StacIO
2727

2828
return StacIO
29+
else:
30+
raise AttributeError(name)
2931

3032

3133
__all__ = [
3234
"Asset",
35+
"ItemAsset",
3336
"Catalog",
3437
"Collection",
35-
"Container",
3638
"DEFAULT_STAC_VERSION",
37-
"DefaultReader",
38-
"DefaultRenderer",
39-
"DefaultWriter",
40-
"Extent",
41-
"Item",
42-
"ItemAsset",
43-
"Link",
39+
"Container",
4440
"PystacError",
4541
"PystacWarning",
46-
"Renderer",
47-
"STACObject",
48-
"SpatialExtent",
4942
"StacError",
5043
"StacWarning",
44+
"Extent",
45+
"SpatialExtent",
5146
"TemporalExtent",
5247
"get_stac_version",
5348
"read_dict",
54-
"read_file",
5549
"set_stac_version",
50+
"read_file",
5651
"write_file",
52+
"Item",
53+
"Link",
54+
"MediaType",
55+
"STACObject",
5756
]

src/pystac/asset.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import copy
4-
from typing import Any
4+
from typing import Any, Protocol, runtime_checkable
55

66
from typing_extensions import Self
77

@@ -77,10 +77,8 @@ def to_dict(self) -> dict[str, Any]:
7777
return d
7878

7979

80-
class AssetsMixin:
81-
"""A mixin for things that have assets (Collections and Items)"""
80+
@runtime_checkable
81+
class Assets(Protocol):
82+
"""A protocol for things that have assets (Collections and Items)"""
8283

8384
assets: dict[str, Asset]
84-
85-
def add_asset(self, key: str, asset: Asset) -> None:
86-
raise NotImplementedError

src/pystac/constants.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
ITEM_TYPE = "Feature"
1818
"""The type field of a JSON STAC Item."""
1919

20-
CHILD_REL = "child"
20+
CHILD = "child"
2121
"""The child relation type, for links."""
22-
ITEM_REL = "item"
22+
ITEM = "item"
2323
"""The item relation type, for links."""
24-
PARENT_REL = "parent"
24+
PARENT = "parent"
2525
"""The parent relation type, for links."""
26-
ROOT_REL = "root"
26+
ROOT = "root"
2727
"""The root relation type, for links."""
28-
SELF_REL = "self"
28+
SELF = "self"
2929
"""The self relation type, for links."""

src/pystac/container.py

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from pathlib import Path
44
from typing import TYPE_CHECKING, Any, Iterator
55

6-
from . import io
7-
from .decorators import v2_deprecated
6+
from . import deprecate
7+
from .constants import CHILD, ITEM
88
from .io import Write
99
from .item import Item
1010
from .link import Link
@@ -50,9 +50,7 @@ def walk(self) -> Iterator[tuple[Container, list[Container], list[Item]]]:
5050
"""
5151
children: list[Container] = []
5252
items: list[Item] = []
53-
for link in filter(
54-
lambda link: link.is_child() or link.is_item(), self.iter_links()
55-
):
53+
for link in self.iter_links(CHILD, ITEM):
5654
stac_object = link.get_stac_object()
5755
if isinstance(stac_object, Container):
5856
children.append(stac_object)
@@ -125,43 +123,33 @@ def get_collections(self, recursive: bool = False) -> Iterator[Collection]:
125123
if recursive and isinstance(stac_object, Container):
126124
yield from stac_object.get_collections(recursive=recursive)
127125

128-
def render(
126+
def save(
129127
self,
130-
root: str | Path,
128+
catalog_type: Any = None,
129+
dest_href: str | Path | None = None,
130+
stac_io: Any = None,
131+
*,
132+
writer: Write | None = None,
131133
) -> None:
132-
"""Renders this container and all of its children and items.
133-
134-
See the [pystac.render][] documentation for more.
135-
136-
Args:
137-
root: The directory at the root of the rendered filesystem tree.
138-
"""
139-
# TODO allow renderer customization
140-
from .render import DefaultRenderer
141-
142-
renderer = DefaultRenderer(str(root))
143-
renderer.render(self)
144-
145-
def save(self, writer: Write | None = None) -> None:
146-
"""Saves this container and all of its children.
147-
148-
This will error if any objects don't have an `href` set. Use
149-
[Container.render][pystac.Container.render] to set those `href` values.
150-
151-
Args:
152-
writer: The writer that will be used for the save operation. If not
153-
provided, this container's writer will be used.
154-
"""
134+
if catalog_type:
135+
deprecate.argument("catalog_type")
136+
if dest_href:
137+
deprecate.argument("dest_href")
138+
if stac_io:
139+
deprecate.argument("stac_io")
155140
if writer is None:
156141
writer = self.writer
157-
io.write_file(self, writer=writer)
142+
143+
self.save_object(stac_io=stac_io, writer=writer)
158144
for stac_object in self.get_children_and_items():
159145
if isinstance(stac_object, Container):
160-
stac_object.save(writer)
146+
stac_object.save(
147+
writer=writer
148+
) # TODO do we need to pass through any of the deprecated arguments?
161149
else:
162-
io.write_file(stac_object, writer=writer)
150+
stac_object.save_object(writer=writer)
163151

164-
@v2_deprecated("Use render() and then save()")
152+
@deprecate.function("Use render() and then save()")
165153
def normalize_and_save(
166154
self,
167155
root_href: str,

src/pystac/decorators.py renamed to src/pystac/deprecate.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,24 @@
33
from typing import Any, Callable
44

55

6-
def v2_deprecated(message: str) -> Callable[..., Any]:
6+
def argument(name: str) -> None:
7+
warnings.warn(
8+
f"Argument {name} is deprecated in PySTAC v2.0 and will be removed in a future "
9+
"version.",
10+
FutureWarning,
11+
)
12+
13+
14+
def module(name: str) -> None:
15+
warnings.warn(
16+
f"Module pystac.{name} is deprecated in PySTAC v2.0 "
17+
"and will be removed in a future "
18+
"version.",
19+
FutureWarning,
20+
)
21+
22+
23+
def function(message: str) -> Callable[..., Any]:
724
def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
825
@wraps(f)
926
def wrapper(*args: Any, **kwargs: Any) -> Any:

src/pystac/extent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
from typing_extensions import Self
99

10+
from . import deprecate
1011
from .constants import DEFAULT_BBOX, DEFAULT_INTERVAL
11-
from .decorators import v2_deprecated
1212
from .errors import StacWarning
1313
from .types import PermissiveBbox, PermissiveInterval
1414

@@ -57,7 +57,7 @@ def from_dict(cls: type[Self], d: dict[str, Any]) -> Self:
5757
return cls(**d)
5858

5959
@classmethod
60-
@v2_deprecated("Use the constructor instead")
60+
@deprecate.function("Use the constructor instead")
6161
def from_coordinates(
6262
cls: type[Self],
6363
coordinates: list[Any],

src/pystac/functions.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from typing import Any
22

3+
from . import deprecate
34
from .constants import DEFAULT_STAC_VERSION
4-
from .decorators import v2_deprecated
55
from .stac_object import STACObject
66

77

8-
@v2_deprecated("Use DEFAULT_STAC_VERSION instead.")
8+
@deprecate.function("Use DEFAULT_STAC_VERSION instead.")
99
def get_stac_version() -> str:
1010
"""**DEPRECATED** Returns the default STAC version.
1111
@@ -24,7 +24,7 @@ def get_stac_version() -> str:
2424
return DEFAULT_STAC_VERSION
2525

2626

27-
@v2_deprecated(
27+
@deprecate.function(
2828
"This function is a no-op. Use `Container.set_stac_version()` to modify the STAC "
2929
"version of an entire catalog."
3030
)
@@ -38,7 +38,7 @@ def set_stac_version(version: str) -> None:
3838
"""
3939

4040

41-
@v2_deprecated("Use STACObject.from_dict instead")
41+
@deprecate.function("Use STACObject.from_dict instead")
4242
def read_dict(d: dict[str, Any]) -> STACObject:
4343
"""**DEPRECATED** Reads a STAC object from a dictionary.
4444

src/pystac/io.py

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""Input and output.
22
33
In PySTAC v2.0, reading and writing STAC objects has been split into separate
4-
protocols, [Read][pystac.io.Read] and [Write][pystac.io.Write] classes. This
5-
should be a transparent operation for most users:
4+
protocols, [Read][pystac.io.Read] and [Write][pystac.io.Write]. This should be
5+
transparent for most users:
66
77
```python
88
catalog = pystac.read_file("catalog.json")
@@ -25,71 +25,69 @@
2525
from pathlib import Path
2626
from typing import Any, Protocol
2727

28+
from . import deprecate
2829
from .errors import PystacError
2930
from .stac_object import STACObject
3031

3132

32-
def read_file(href: str | Path, reader: Read | None = None) -> STACObject:
33+
def read_file(
34+
href: str | Path,
35+
stac_io: Any = None,
36+
*,
37+
reader: Read | None = None,
38+
) -> STACObject:
3339
"""Reads a file from a href.
3440
35-
Uses the default [Reader][pystac.DefaultReader].
36-
3741
Args:
3842
href: The href to read
43+
reader: The [Read][pystac.Read] to use for reading
3944
4045
Returns:
4146
The STAC object
42-
43-
Examples:
44-
>>> item = pystac.read_file("item.json")
4547
"""
48+
if stac_io:
49+
deprecate.argument("stac_io")
4650
return STACObject.from_file(href, reader=reader)
4751

4852

4953
def write_file(
50-
stac_object: STACObject,
54+
obj: STACObject,
55+
include_self_link: bool | None = None,
56+
dest_href: str | Path | None = None,
57+
stac_io: Any = None,
5158
*,
52-
href: str | Path | None = None,
5359
writer: Write | None = None,
5460
) -> None:
5561
"""Writes a STAC object to a file, using its href.
5662
5763
If the href is not set, this will throw and error.
5864
5965
Args:
60-
stac_object: The STAC object to write
66+
obj: The STAC object to write
67+
dest_href: The href to write the STAC object to
68+
writer: The [Write][pystac.Write] to use for writing
6169
"""
70+
if include_self_link is not None:
71+
deprecate.argument("include_self_link")
72+
if stac_io:
73+
deprecate.argument("stac_io")
74+
6275
if writer is None:
6376
writer = DefaultWriter()
64-
if href is None:
65-
href = stac_object.href
66-
if href is None:
67-
raise PystacError(f"cannot write {stac_object} without an href")
68-
data = stac_object.to_dict()
69-
if isinstance(href, Path):
70-
writer.write_json_to_path(data, href)
77+
78+
if dest_href is None:
79+
dest_href = obj.href
80+
if dest_href is None:
81+
raise PystacError(f"cannot write {obj} without an href")
82+
d = obj.to_dict()
83+
if isinstance(dest_href, Path):
84+
writer.write_json_to_path(d, dest_href)
7185
else:
72-
url = urllib.parse.urlparse(href)
86+
url = urllib.parse.urlparse(dest_href)
7387
if url.scheme:
74-
writer.write_json_to_url(data, href)
88+
writer.write_json_to_url(d, dest_href)
7589
else:
76-
writer.write_json_to_path(data, Path(href))
77-
78-
79-
def make_absolute_href(href: str, base: str | None) -> str:
80-
if urllib.parse.urlparse(href).scheme:
81-
return href # TODO file:// schemes
82-
83-
if base:
84-
if urllib.parse.urlparse(base).scheme:
85-
raise NotImplementedError("url joins not implemented yet, should be easy")
86-
else:
87-
if base.endswith("/"): # TODO windoze
88-
return str((Path(base) / href).resolve(strict=False))
89-
else:
90-
return str((Path(base).parent / href).resolve(strict=False))
91-
else:
92-
raise NotImplementedError
90+
writer.write_json_to_path(d, Path(dest_href))
9391

9492

9593
class Read(Protocol):

0 commit comments

Comments
 (0)