Skip to content

Commit 7f9fef4

Browse files
author
Jon Duckworth
authored
Merge pull request #477 from stac-utils/main
Merge v1.0.0-rc.2 changes into 1.0 release branch
2 parents 4b80218 + f742fa4 commit 7f9fef4

12 files changed

+369
-121
lines changed

CHANGELOG.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@
1212

1313
### Deprecated
1414

15+
## [v1.0.0-rc.2]
16+
17+
### Added
18+
19+
- Add a `preserve_dict` parameter to `ItemCollection.from_dict` and set it to False when
20+
using `ItemCollection.from_file`.
21+
([#468](https://github.com/stac-utils/pystac/pull/468))
22+
- `StacIO.json_dumps` and `StacIO.json_loads` methods for JSON
23+
serialization/deserialization. These were "private" methods, but are now "public" and
24+
documented ([#471](https://github.com/stac-utils/pystac/pull/471))
25+
26+
### Changed
27+
28+
- `pystac.stac_io.DuplicateObjectKeyError` moved to `pystac.DuplicateObjectKeyError`
29+
([#471](https://github.com/stac-utils/pystac/pull/471))
30+
1531
## [v1.0.0-rc.1]
1632

1733
### Added
@@ -381,7 +397,8 @@ use `Band.create`
381397

382398
Initial release.
383399

384-
[Unreleased]: <https://github.com/stac-utils/pystac/compare/v1.0.0-rc.1..main>
400+
[Unreleased]: <https://github.com/stac-utils/pystac/compare/v1.0.0-rc.2..main>
401+
[v1.0.0-rc.2]: <https://github.com/stac-utils/pystac/compare/v1.0.0-rc.1..v1.0.0-rc.2>
385402
[v1.0.0-rc.1]: <https://github.com/stac-utils/pystac/compare/v1.0.0-beta.3..v1.0.0-rc.1>
386403
[v1.0.0-beta.3]: <https://github.com/stac-utils/pystac/compare/v1.0.0-beta.2..v1.0.0-beta.3>
387404
[v1.0.0-beta.2]: <https://github.com/stac-utils/pystac/compare/v1.0.0-beta.1..v1.0.0-beta.2>

docs/api.rst

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,20 @@ StacIO
171171
:members:
172172
:undoc-members:
173173

174+
DefaultStacIO
175+
~~~~~~~~~~~~~
176+
177+
.. autoclass:: pystac.stac_io.DefaultStacIO
178+
:members:
179+
:show-inheritance:
180+
181+
DuplicateKeyReportingMixin
182+
~~~~~~~~~~~~~~~~~~~~~~~~~~
183+
184+
.. autoclass:: pystac.stac_io.DuplicateKeyReportingMixin
185+
:members:
186+
:show-inheritance:
187+
174188
STAC_IO
175189
~~~~~~~
176190

@@ -213,11 +227,47 @@ STACError
213227

214228
.. autoclass:: pystac.STACError
215229

230+
STACTypeError
231+
~~~~~~~~~~~~~
232+
233+
.. autoclass:: pystac.STACTypeError
234+
235+
DuplicateObjectKeyError
236+
~~~~~~~~~~~~~~~~~~~~~~~
237+
238+
.. autoclass:: pystac.DuplicateObjectKeyError
239+
240+
ExtensionAlreadyExistsError
241+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
242+
243+
.. autoclass:: pystac.ExtensionAlreadyExistsError
244+
245+
ExtensionTypeError
246+
~~~~~~~~~~~~~~~~~~
247+
248+
.. autoclass:: pystac.ExtensionTypeError
249+
250+
ExtensionNotImplemented
251+
~~~~~~~~~~~~~~~~~~~~~~~
252+
253+
.. autoclass:: pystac.ExtensionNotImplemented
254+
216255
ExtensionTypeError
217256
~~~~~~~~~~~~~~~~~~
218257

219258
.. autoclass:: pystac.ExtensionTypeError
220259

260+
RequiredPropertyMissing
261+
~~~~~~~~~~~~~~~~~~~~~~~
262+
263+
.. autoclass:: pystac.RequiredPropertyMissing
264+
265+
STACValidationError
266+
~~~~~~~~~~~~~~~~~~~
267+
268+
.. autoclass:: pystac.STACValidationError
269+
270+
221271
Extensions
222272
----------
223273

docs/concepts.rst

Lines changed: 77 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -225,72 +225,96 @@ written (e.g. if you are working with self-contained catalogs).
225225

226226
.. _using stac_io:
227227

228-
Using STAC_IO
228+
I/O in PySTAC
229229
=============
230230

231-
The :class:`~pystac.STAC_IO` class is the way PySTAC reads and writes text from file
232-
locations. Since PySTAC aims to be dependency-free, there is no default mechanisms to
233-
read and write from anything but the local file system. However, users of PySTAC may
234-
want to read and write from other file systems, such as HTTP or cloud object storage.
235-
STAC_IO allows users to hook into PySTAC and define their own reading and writing
236-
primitives to allow for those use cases.
237-
238-
To enable reading from other types of file systems, it is recommended that in the
239-
`__init__.py` of the client module, or at the beginning of the script using PySTAC, you
240-
overwrite the :func:`STAC_IO.read_text_method <pystac.STAC_IO.read_text_method>` and
241-
:func:`STAC_IO.write_text_method <pystac.STAC_IO.write_text_method>` members of STAC_IO
242-
with functions that read and write however you need. For example, this code will allow
231+
The :class:`pystac.StacIO` class defines fundamental methods for I/O
232+
operations within PySTAC, including serialization and deserialization to and from
233+
JSON files and conversion to and from Python dictionaries. This is an abstract class
234+
and should not be instantiated directly. However, PySTAC provides a
235+
:class:`pystac.stac_io.DefaultStacIO` class with minimal implementations of these
236+
methods. This default implementation provides support for reading and writing files
237+
from the local filesystem as well as HTTP URIs (using ``urllib``). This class is
238+
created automatically by all of the object-specific I/O methods (e.g.
239+
:meth:`pystac.Catalog.from_file`), so most users will not need to instantiate this
240+
class themselves.
241+
242+
If you require custom logic for I/O operations or would like to use a 3rd-party library
243+
for I/O operations (e.g. ``requests``), you can create a sub-class of
244+
:class:`pystac.StacIO` (or :class:`pystac.DefaultStacIO`) and customize the methods as
245+
you see fit. You can then pass instances of this custom sub-class into the ``stac_io``
246+
argument of most object-specific I/O methods. You can also use
247+
:meth:`pystac.StacIO.set_default` in your client's ``__init__.py`` file to make this
248+
sub-class the default :class:`pystac.StacIO` implementation throughout the library.
249+
250+
For example, this code will allow
243251
for reading from AWS's S3 cloud object storage using `boto3
244-
<https://boto3.amazonaws.com/v1/documentation/api/latest/index.html>`_:
252+
<https://boto3.amazonaws.com/v1/documentation/api/latest/index.html>`__:
245253

246254
.. code-block:: python
247255
248256
from urllib.parse import urlparse
249257
import boto3
250-
from pystac import STAC_IO
251-
252-
def my_read_method(uri):
253-
parsed = urlparse(uri)
254-
if parsed.scheme == 's3':
255-
bucket = parsed.netloc
256-
key = parsed.path[1:]
257-
s3 = boto3.resource('s3')
258-
obj = s3.Object(bucket, key)
259-
return obj.get()['Body'].read().decode('utf-8')
260-
else:
261-
return STAC_IO.default_read_text_method(uri)
262-
263-
def my_write_method(uri, txt):
264-
parsed = urlparse(uri)
265-
if parsed.scheme == 's3':
266-
bucket = parsed.netloc
267-
key = parsed.path[1:]
268-
s3 = boto3.resource("s3")
269-
s3.Object(bucket, key).put(Body=txt)
270-
else:
271-
STAC_IO.default_write_text_method(uri, txt)
272-
273-
STAC_IO.read_text_method = my_read_method
274-
STAC_IO.write_text_method = my_write_method
275-
276-
If you are only going to read from another source, e.g. HTTP, you could only replace the
277-
read method. For example, using the `requests library
278-
<https://requests.kennethreitz.org/en/master>`_:
258+
from pystac import Link
259+
from pystac.stac_io import DefaultStacIO, StacIO
260+
261+
class CustomStacIO(DefaultStacIO):
262+
def __init__():
263+
self.s3 = boto3.resource("s3")
264+
265+
def read_text(
266+
self, source: Union[str, Link], *args: Any, **kwargs: Any
267+
) -> str:
268+
parsed = urlparse(uri)
269+
if parsed.scheme == "s3":
270+
bucket = parsed.netloc
271+
key = parsed.path[1:]
272+
273+
obj = self.s3.Object(bucket, key)
274+
return obj.get()["Body"].read().decode("utf-8")
275+
else:
276+
return super().read_text(source, *args, **kwargs)
277+
278+
def write_text(
279+
self, dest: Union[str, Link], txt: str, *args: Any, **kwargs: Any
280+
) -> None:
281+
parsed = urlparse(uri)
282+
if parsed.scheme == "s3":
283+
bucket = parsed.netloc
284+
key = parsed.path[1:]
285+
s3 = boto3.resource("s3")
286+
s3.Object(bucket, key).put(Body=txt, ContentEncoding="utf-8")
287+
else:
288+
super().write_text(dest, txt, *args, **kwargs)
289+
290+
StacIO.set_default(CustomStacIO)
291+
292+
293+
If you only need to customize read operations you can inherit from
294+
:class:`~pystac.stac_io.DefaultStacIO` and only overwrite the read method. For example,
295+
to take advantage of connection pooling using a `requests.Session
296+
<https://requests.kennethreitz.org/en/master>`__:
279297

280298
.. code-block:: python
281299
282300
from urllib.parse import urlparse
283301
import requests
284-
from pystac import STAC_IO
285-
286-
def my_read_method(uri):
287-
parsed = urlparse(uri)
288-
if parsed.scheme.startswith('http'):
289-
return requests.get(uri).text
290-
else:
291-
return STAC_IO.default_read_text_method(uri)
292-
293-
STAC_IO.read_text_method = my_read_method
302+
from pystac.stac_io import DefaultStacIO, StacIO
303+
304+
class ConnectionPoolingIO(DefaultStacIO):
305+
def __init__():
306+
self.session = requests.Session()
307+
308+
def read_text(
309+
self, source: Union[str, Link], *args: Any, **kwargs: Any
310+
) -> str:
311+
parsed = urlparse(uri)
312+
if parsed.scheme.startswith("http"):
313+
return self.session.get(uri).text
314+
else:
315+
return super().read_text(source, *args, **kwargs)
316+
317+
StacIO.set_default(ConnectionPoolingIO)
294318
295319
Validation
296320
==========

pystac/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pystac.errors import (
77
STACError,
88
STACTypeError,
9+
DuplicateObjectKeyError,
910
ExtensionAlreadyExistsError,
1011
ExtensionNotImplemented,
1112
ExtensionTypeError,

pystac/errors.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ class STACTypeError(Exception):
1919
pass
2020

2121

22+
class DuplicateObjectKeyError(Exception):
23+
"""Raised when deserializing a JSON object containing a duplicate key."""
24+
25+
pass
26+
27+
2228
class ExtensionTypeError(Exception):
2329
"""An ExtensionTypeError is raised when an extension is used against
2430
an object that the extension does not apply to

pystac/item_collection.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,16 +134,26 @@ def clone(self) -> "ItemCollection":
134134
)
135135

136136
@classmethod
137-
def from_dict(cls, d: Dict[str, Any]) -> "ItemCollection":
137+
def from_dict(
138+
cls, d: Dict[str, Any], preserve_dict: bool = True
139+
) -> "ItemCollection":
138140
"""Creates a :class:`ItemCollection` instance from a dictionary.
139141
140142
Arguments:
141143
d : The dictionary from which the :class:`~ItemCollection` will be created
144+
preserve_dict: If False, the dict parameter ``d`` may be modified
145+
during this method call. Otherwise the dict is not mutated.
146+
Defaults to True, which results results in a deepcopy of the
147+
parameter. Set to False when possible to avoid the performance
148+
hit of a deepcopy.
142149
"""
143150
if not cls.is_item_collection(d):
144151
raise STACTypeError("Dict is not a valid ItemCollection")
145152

146-
items = [pystac.Item.from_dict(item) for item in d.get("features", [])]
153+
items = [
154+
pystac.Item.from_dict(item, preserve_dict=preserve_dict)
155+
for item in d.get("features", [])
156+
]
147157
extra_fields = {k: v for k, v in d.items() if k not in ("features", "type")}
148158

149159
return cls(items=items, extra_fields=extra_fields)
@@ -166,7 +176,7 @@ def from_file(
166176

167177
d = stac_io.read_json(href)
168178

169-
return cls.from_dict(d)
179+
return cls.from_dict(d, preserve_dict=False)
170180

171181
def save_object(
172182
self,

0 commit comments

Comments
 (0)