Skip to content

Commit c4cbd0d

Browse files
author
Phil Varner
authored
Merge pull request #57 from stac-utils/pv/create_indexes_at_startup
create indexes at startup rather than when ingesting, add collection index schema
2 parents 213f292 + 8c16cfa commit c4cbd0d

File tree

5 files changed

+156
-116
lines changed

5 files changed

+156
-116
lines changed

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from stac_fastapi.elasticsearch.config import ElasticsearchSettings
55
from stac_fastapi.elasticsearch.core import CoreCrudClient
66
from stac_fastapi.elasticsearch.extensions import QueryExtension
7+
from stac_fastapi.elasticsearch.indexes import IndexesClient
78
from stac_fastapi.elasticsearch.session import Session
89
from stac_fastapi.elasticsearch.transactions import (
910
BulkTransactionsClient,
@@ -43,6 +44,11 @@
4344
app = api.app
4445

4546

47+
@app.on_event("startup")
48+
async def _startup_event():
49+
IndexesClient().create_indexes()
50+
51+
4652
def run():
4753
"""Run app from command line using uvicorn if available."""
4854
try:

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/core.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import elasticsearch
1010
from elasticsearch_dsl import Q, Search
1111
from fastapi import HTTPException
12+
from overrides import overrides
1213

1314
# from geojson_pydantic.geometries import Polygon
1415
from pydantic import ValidationError
@@ -22,13 +23,15 @@
2223
# from stac_fastapi.elasticsearch.types.error_checks import ErrorChecks
2324
from stac_fastapi.types.core import BaseCoreClient
2425
from stac_fastapi.types.errors import NotFoundError
25-
from stac_fastapi.types.search import BaseSearchPostRequest
2626
from stac_fastapi.types.stac import Collection, Collections, Item, ItemCollection
2727

2828
logger = logging.getLogger(__name__)
2929

3030
NumType = Union[float, int]
3131

32+
ITEMS_INDEX = "stac_items"
33+
COLLECTIONS_INDEX = "stac_collections"
34+
3235

3336
@attr.s
3437
class CoreCrudClient(BaseCoreClient):
@@ -44,17 +47,13 @@ class CoreCrudClient(BaseCoreClient):
4447
settings = ElasticsearchSettings()
4548
client = settings.create_client
4649

47-
@staticmethod
48-
def _lookup_id(id: str, table, session):
49-
"""Lookup row by id."""
50-
pass
51-
50+
@overrides
5251
def all_collections(self, **kwargs) -> Collections:
5352
"""Read all collections from the database."""
5453
base_url = str(kwargs["request"].base_url)
5554
try:
5655
collections = self.client.search(
57-
index="stac_collections", doc_type="_doc", query={"match_all": {}}
56+
index=COLLECTIONS_INDEX, query={"match_all": {}}
5857
)
5958
except elasticsearch.exceptions.NotFoundError:
6059
raise NotFoundError("No collections exist")
@@ -86,18 +85,20 @@ def all_collections(self, **kwargs) -> Collections:
8685
)
8786
return collection_list
8887

88+
@overrides
8989
def get_collection(self, collection_id: str, **kwargs) -> Collection:
9090
"""Get collection by id."""
9191
base_url = str(kwargs["request"].base_url)
9292
try:
93-
collection = self.client.get(index="stac_collections", id=collection_id)
93+
collection = self.client.get(index=COLLECTIONS_INDEX, id=collection_id)
9494
except elasticsearch.exceptions.NotFoundError:
9595
raise NotFoundError(f"Collection {collection_id} not found")
9696

9797
return self.collection_serializer.db_to_stac(collection["_source"], base_url)
9898

99+
@overrides
99100
def item_collection(
100-
self, collection_id: str, limit: int = 10, **kwargs
101+
self, collection_id: str, limit: int = 10, token: str = None, **kwargs
101102
) -> ItemCollection:
102103
"""Read an item collection from the database."""
103104
links = []
@@ -136,11 +137,12 @@ def item_collection(
136137
context=context_obj,
137138
)
138139

140+
@overrides
139141
def get_item(self, item_id: str, collection_id: str, **kwargs) -> Item:
140142
"""Get item by item id, collection id."""
141143
base_url = str(kwargs["request"].base_url)
142144
try:
143-
item = self.client.get(index="stac_items", id=item_id)
145+
item = self.client.get(index=ITEMS_INDEX, id=item_id)
144146
except elasticsearch.exceptions.NotFoundError:
145147
raise NotFoundError(
146148
f"Item {item_id} does not exist in Collection {collection_id}"
@@ -171,6 +173,7 @@ def _return_date(interval_str):
171173

172174
return {"lte": end_date, "gte": start_date}
173175

176+
@overrides
174177
def get_search(
175178
self,
176179
collections: Optional[List[str]] = None,
@@ -234,15 +237,13 @@ def bbox2poly(b0, b1, b2, b3):
234237
poly = [[[b0, b1], [b2, b1], [b2, b3], [b0, b3], [b0, b1]]]
235238
return poly
236239

237-
def post_search(
238-
self, search_request: BaseSearchPostRequest, **kwargs
239-
) -> ItemCollection:
240+
def post_search(self, search_request: Search, **kwargs) -> ItemCollection:
240241
"""POST search catalog."""
241242
base_url = str(kwargs["request"].base_url)
242243
search = (
243244
Search()
244245
.using(self.client)
245-
.index("stac_items")
246+
.index(ITEMS_INDEX)
246247
.sort(
247248
{"properties.datetime": {"order": "desc"}},
248249
{"id": {"order": "desc"}},
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""index management client."""
2+
3+
import logging
4+
5+
import attr
6+
7+
from stac_fastapi.elasticsearch.config import ElasticsearchSettings
8+
from stac_fastapi.elasticsearch.core import COLLECTIONS_INDEX, ITEMS_INDEX
9+
from stac_fastapi.elasticsearch.session import Session
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
@attr.s
15+
class IndexesClient:
16+
"""Elasticsearch client to handle index creation."""
17+
18+
session: Session = attr.ib(default=attr.Factory(Session.create_from_env))
19+
client = ElasticsearchSettings().create_client
20+
21+
ES_MAPPINGS_DYNAMIC_TEMPLATES = [
22+
# Common https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md
23+
{
24+
"descriptions": {
25+
"match_mapping_type": "string",
26+
"match": "description",
27+
"mapping": {"type": "text"},
28+
}
29+
},
30+
{
31+
"titles": {
32+
"match_mapping_type": "string",
33+
"match": "title",
34+
"mapping": {"type": "text"},
35+
}
36+
},
37+
# Projection Extension https://github.com/stac-extensions/projection
38+
{"proj_epsg": {"match": "proj:epsg", "mapping": {"type": "integer"}}},
39+
{
40+
"proj_projjson": {
41+
"match": "proj:projjson",
42+
"mapping": {"type": "object", "enabled": False},
43+
}
44+
},
45+
{
46+
"proj_centroid": {
47+
"match": "proj:centroid",
48+
"mapping": {"type": "geo_point"},
49+
}
50+
},
51+
{
52+
"proj_geometry": {
53+
"match": "proj:geometry",
54+
"mapping": {"type": "geo_shape"},
55+
}
56+
},
57+
{
58+
"no_index_href": {
59+
"match": "href",
60+
"mapping": {"type": "text", "index": False},
61+
}
62+
},
63+
# Default all other strings not otherwise specified to keyword
64+
{"strings": {"match_mapping_type": "string", "mapping": {"type": "keyword"}}},
65+
{"numerics": {"match_mapping_type": "long", "mapping": {"type": "float"}}},
66+
]
67+
68+
ES_ITEMS_MAPPINGS = {
69+
"numeric_detection": False,
70+
"dynamic_templates": ES_MAPPINGS_DYNAMIC_TEMPLATES,
71+
"properties": {
72+
"geometry": {"type": "geo_shape"},
73+
"assets": {"type": "object", "enabled": False},
74+
"links": {"type": "object", "enabled": False},
75+
"properties": {
76+
"type": "object",
77+
"properties": {
78+
# Common https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md
79+
"datetime": {"type": "date"},
80+
"start_datetime": {"type": "date"},
81+
"end_datetime": {"type": "date"},
82+
"created": {"type": "date"},
83+
"updated": {"type": "date"},
84+
# Satellite Extension https://github.com/stac-extensions/sat
85+
"sat:absolute_orbit": {"type": "integer"},
86+
"sat:relative_orbit": {"type": "integer"},
87+
},
88+
},
89+
},
90+
}
91+
92+
ES_COLLECTIONS_MAPPINGS = {
93+
"numeric_detection": False,
94+
"dynamic_templates": ES_MAPPINGS_DYNAMIC_TEMPLATES,
95+
"properties": {
96+
"extent.spatial.bbox": {"type": "long"},
97+
"extent.temporal.interval": {"type": "date"},
98+
"providers": {"type": "object", "enabled": False},
99+
"links": {"type": "object", "enabled": False},
100+
"item_assets": {"type": "object", "enabled": False},
101+
},
102+
}
103+
104+
def create_indexes(self):
105+
"""Create the index for Items and Collections."""
106+
self.client.indices.create(
107+
index=ITEMS_INDEX,
108+
body={"mappings": self.ES_ITEMS_MAPPINGS},
109+
ignore=400, # ignore 400 already exists code
110+
)
111+
self.client.indices.create(
112+
index=COLLECTIONS_INDEX,
113+
body={"mappings": self.ES_COLLECTIONS_MAPPINGS},
114+
ignore=400, # ignore 400 already exists code
115+
)

0 commit comments

Comments
 (0)