Skip to content

Commit 63f6504

Browse files
authored
Merge pull request #84 from azavea/master
Merge 0.3.3 changes into 0.3 branch.
2 parents 07d4bad + 6ee2ba8 commit 63f6504

File tree

120 files changed

+19883
-891
lines changed

Some content is hidden

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

120 files changed

+19883
-891
lines changed

.style.yapf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[style]
2+
column_limit = 100

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@
88

99
### Fixed
1010

11+
## [v0.3.3] - 2020-02-05
12+
13+
### Added
14+
15+
- Allow for backwards compatibilty for reading STAC [#77](https://github.com/azavea/pystac/pull/70)
16+
17+
### Fixed
18+
19+
- Fix issue with multiple collection reads per item [#79](https://github.com/azavea/pystac/pull/79)
20+
- Fix issue with iteration of children in `catalog.walk` [#78](https://github.com/azavea/pystac/pull/78)
21+
- Allow v0.7.0 sar items to fit in version range [#80](https://github.com/azavea/pystac/pull/80)
22+
1123
## [v0.3.2] - 2020-01-28
1224

1325
### Added

pystac/__init__.py

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,53 +15,18 @@ class STACError(Exception):
1515

1616
from pystac.version import (__version__, STAC_VERSION)
1717
from pystac.stac_io import STAC_IO
18+
from pystac.extension import Extension
1819
from pystac.stac_object import STACObject
1920
from pystac.media_type import MediaType
2021
from pystac.link import (Link, LinkType)
2122
from pystac.catalog import (Catalog, CatalogType)
22-
from pystac.collection import (Collection, Extent, SpatialExtent,
23-
TemporalExtent, Provider)
23+
from pystac.collection import (Collection, Extent, SpatialExtent, TemporalExtent, Provider)
2424
from pystac.item import (Item, Asset)
2525
from pystac.item_collection import ItemCollection
2626
from pystac.single_file_stac import SingleFileSTAC
2727
from pystac.eo import *
2828
from pystac.label import *
2929

30-
from pystac.serialization import (identify_stac_object, STACObjectType)
30+
from pystac.serialization import stac_object_from_dict
3131

32-
33-
def _stac_object_from_dict(d, href=None, root=None):
34-
"""Determines how to deserialize a dictionary into a STAC object.
35-
36-
Args:
37-
d (dict): The dict to parse.
38-
href (str): Optional href that is the file location of the object being
39-
parsed.
40-
root (Catalog or Collection): Optional root of the catalog for this object.
41-
If provided, the root's resolved object cache can be used to search for
42-
previously resolved instances of the STAC object.
43-
44-
Note: This is used internally in STAC_IO to deserialize STAC Objects.
45-
It is in the top level __init__ in order to avoid circular dependencies.
46-
"""
47-
info = identify_stac_object(d)
48-
49-
# TODO: Transorm older versions to newest version (pystac.serialization.migrate)
50-
51-
if info.object_type == STACObjectType.CATALOG:
52-
return Catalog.from_dict(d, href=href, root=root)
53-
if info.object_type == STACObjectType.COLLECTION:
54-
return Collection.from_dict(d, href=href, root=root)
55-
if info.object_type == STACObjectType.ITEMCOLLECTION:
56-
if 'single-file-stac' in info.common_extensions:
57-
return SingleFileSTAC.from_dict(d, href=href, root=root)
58-
return ItemCollection.from_dict(d, href=href, root=root)
59-
if info.object_type == STACObjectType.ITEM:
60-
if 'eo' in info.common_extensions:
61-
return EOItem.from_dict(d, href=href, root=root)
62-
if 'label' in info.common_extensions:
63-
return LabelItem.from_dict(d, href=href, root=root)
64-
return Item.from_dict(d, href=href, root=root)
65-
66-
67-
STAC_IO.stac_object_from_dict = _stac_object_from_dict
32+
STAC_IO.stac_object_from_dict = stac_object_from_dict

pystac/cache.py

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
from collections import ChainMap
2+
from copy import copy
3+
4+
5+
class ResolvedObjectCache:
6+
"""This class tracks resolved objects tied to root catalogs.
7+
A STAC object is 'resolved' when it is a Python Object; a link
8+
to a STAC object such as a Catalog or Item is considered "unresolved"
9+
if it's target is pointed at an HREF of the object.
10+
11+
Tracking resolved objects allows us to tie together the same instances
12+
when there are loops in the Graph of the STAC catalog (e.g. a LabelItem
13+
can link to a rel:source, and if that STAC Item exists in the same
14+
root catalog they should refer to the same Python object).
15+
16+
Resolution tracking is important when copying STACs in-memory: In order
17+
for object links to refer to the copy of STAC Objects rather than their
18+
originals, we have to keep track of the resolved STAC Objects and replace
19+
them with their copies.
20+
21+
Args:
22+
ids_to_objects (Dict[str, STACObject]): Existing cache of STACObject IDs mapped
23+
to the cached STACObject.
24+
TODO
25+
"""
26+
def __init__(self, ids_to_objects=None, ids_to_hrefs=None, hrefs_to_ids=None):
27+
self.ids_to_objects = ids_to_objects or {}
28+
self.ids_to_hrefs = ids_to_hrefs or {}
29+
self.hrefs_to_ids = hrefs_to_ids or {}
30+
31+
self._collection_cache = None
32+
33+
def _cache_href(self, obj):
34+
href = obj.get_self_href()
35+
if href is not None:
36+
self.ids_to_hrefs[obj.id] = href
37+
self.hrefs_to_ids[href] = obj.id
38+
39+
def get_or_cache(self, obj):
40+
"""Gets the STACObject that is the cached version of the given STACObject; or, if
41+
none exists, sets the cached object to the given object.
42+
43+
Args:
44+
obj (STACObject): The given object who's ID will be checked against the cache.
45+
46+
Returns:
47+
STACObject: Either the cached object that has the same ID as the given
48+
object, or the given object.
49+
"""
50+
if obj.id in self.ids_to_objects:
51+
return self.ids_to_objects[obj.id]
52+
else:
53+
self.ids_to_objects[obj.id] = obj
54+
self._cache_href(obj)
55+
return obj
56+
57+
def get(self, obj):
58+
"""Get the cached object that has the same ID as the given object.
59+
60+
Args:
61+
obj (STACObject): The given object who's ID will be checked against the cache.
62+
63+
Returns:
64+
STACObject or None: Either the cached object that has the same ID as the given
65+
object, or None
66+
"""
67+
return self.get_by_id(obj.id)
68+
69+
def get_by_id(self, obj_id):
70+
"""Get the cached object that has the given ID.
71+
72+
Args:
73+
obj_id (str): The ID to be checked against the cache.
74+
75+
Returns:
76+
STACObject or None: Either the cached object that has the given ID, or None
77+
"""
78+
79+
return self.ids_to_objects.get(obj_id)
80+
81+
def get_by_href(self, href):
82+
obj_id = self.hrefs_to_ids.get(href)
83+
if obj_id is not None:
84+
return self.get_by_id(obj_id)
85+
else:
86+
return None
87+
88+
def cache(self, obj):
89+
"""Set the given object into the cache.
90+
91+
Args:
92+
obj (STACObject): The object to cache
93+
"""
94+
self.ids_to_objects[obj.id] = obj
95+
self._cache_href(obj)
96+
97+
def remove(self, obj):
98+
"""Removes any cached object that matches the given object's id.
99+
100+
Args:
101+
obj (STACObject): The object to remove
102+
"""
103+
self.remove_by_id(obj.id)
104+
105+
def remove_by_id(self, obj_id):
106+
"""Removes any cached object that matches the given ID.
107+
108+
Args:
109+
obj_id (str): The object ID to remove
110+
"""
111+
self.ids_to_objects.pop(obj_id, None)
112+
href = self.ids_to_hrefs.pop(obj_id, None)
113+
if href is not None:
114+
self.hrefs_to_ids.pop(href, None)
115+
116+
def clone(self):
117+
"""Clone this ResolvedObjectCache
118+
119+
Returns:
120+
ResolvedObjectCache: A clone of this cache, which contains a shallow
121+
copy of the ID to STACObject cache.
122+
"""
123+
return ResolvedObjectCache(copy(self.ids_to_objects), copy(self.ids_to_hrefs),
124+
copy(self.hrefs_to_ids))
125+
126+
def __contains__(self, obj):
127+
return self.contains_id(obj.id)
128+
129+
def contains_id(self, obj_id):
130+
return obj_id in self.ids_to_objects
131+
132+
def as_collection_cache(self):
133+
if self._collection_cache is None:
134+
self._collection_cache = ResolvedObjectCollectionCache(self)
135+
return self._collection_cache
136+
137+
@staticmethod
138+
def merge(first, second):
139+
"""Merges two ResolvedObjectCache.
140+
141+
The merged cache will give preference to the first argument; that is, if there
142+
are cached IDs that exist in both the first and second cache, the object cached
143+
in the first will be cached in the resulting merged ResolvedObjectCache.
144+
145+
Args:
146+
first (ResolvedObjectCache): The first cache to merge. This cache will be
147+
the prefered cache for objects in the case of ID conflicts.
148+
second (ResolvedObjectCache): The second cache to merge.
149+
150+
Returns:
151+
ResolvedObjectCache: The resulting merged cache.
152+
"""
153+
merged = ResolvedObjectCache(
154+
ids_to_objects=dict(ChainMap(copy(first.ids_to_objects), copy(second.ids_to_objects))),
155+
ids_to_hrefs=dict(ChainMap(copy(first.ids_to_hrefs), copy(second.ids_to_hrefs))),
156+
hrefs_to_ids=dict(ChainMap(copy(first.hrefs_to_ids), copy(second.hrefs_to_ids))))
157+
158+
merged._collection_cache = ResolvedObjectCollectionCache.merge(
159+
merged, first._collection_cache, second._collection_cache)
160+
161+
return merged
162+
163+
164+
class CollectionCache:
165+
"""Cache of collections that can be used to avoid re-reading Collection
166+
JSON in :func:`pystac.serialization.merge_common_properties
167+
<pystac.serialization.common_properties.merge_common_properties>`.
168+
The CollectionCache will contain collections as either as dicts or PySTAC Collections,
169+
and will set Collection JSON that it reads in order to merge in common properties.
170+
"""
171+
def __init__(self, cached_ids=None, cached_hrefs=None):
172+
self.cached_ids = cached_ids or {}
173+
self.cached_hrefs = cached_hrefs or {}
174+
175+
def get_by_id(self, collection_id):
176+
return self.cached_ids.get(collection_id)
177+
178+
def get_by_href(self, href):
179+
return self.cached_hrefs.get(href)
180+
181+
def contains_id(self, collection_id):
182+
return collection_id in self.cached_ids
183+
184+
def cache(self, collection, href=None):
185+
"""Caches a collection JSON."""
186+
self.cached_ids[collection['id']] = collection
187+
188+
if href is not None:
189+
self.cached_hrefs[href] = collection
190+
191+
192+
class ResolvedObjectCollectionCache(CollectionCache):
193+
def __init__(self, resolved_object_cache, cached_ids=None, cached_hrefs=None):
194+
super().__init__(cached_ids, cached_hrefs)
195+
self.resolved_object_cache = resolved_object_cache
196+
197+
def get_by_id(self, collection_id):
198+
result = self.resolved_object_cache.get_by_id(collection_id)
199+
if result is None:
200+
return super().get_by_id(collection_id)
201+
else:
202+
return result
203+
204+
def get_by_href(self, href):
205+
result = self.resolved_object_cache.get_by_href(href)
206+
if result is None:
207+
return super().get_by_href(href)
208+
else:
209+
return result
210+
211+
def contains_id(self, collection_id):
212+
return (self.resolved_object_cache.contains_id(collection_id)
213+
or super().contains_id(collection_id))
214+
215+
def cache(self, collection, href=None):
216+
super().cache(collection, href)
217+
218+
@staticmethod
219+
def merge(resolved_object_cache, first, second):
220+
first_cached_ids = {}
221+
if first is not None:
222+
first_cached_ids = copy(first.cached_ids)
223+
224+
second_cached_ids = {}
225+
if second is not None:
226+
second_cached_ids = copy(second.cached_ids)
227+
228+
first_cached_hrefs = {}
229+
if first is not None:
230+
first_cached_hrefs = copy(first.cached_hrefs)
231+
232+
second_cached_hrefs = {}
233+
if second is not None:
234+
second_cached_hrefs = copy(second.cached_hrefs)
235+
236+
return ResolvedObjectCollectionCache(
237+
resolved_object_cache,
238+
cached_ids=dict(ChainMap(first_cached_ids, second_cached_ids)),
239+
cached_hrefs=dict(ChainMap(first_cached_hrefs, second_cached_hrefs)))

0 commit comments

Comments
 (0)