stac-fastapi.core 7.2.0__tar.gz → 8.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/PKG-INFO +9 -9
  2. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/pyproject.toml +8 -8
  3. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/base_database_logic.py +200 -34
  4. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/catalogs_client.py +255 -171
  5. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/core.py +671 -172
  6. stac_fastapi_core-8.0.0/stac_fastapi/core/etag.py +265 -0
  7. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/extensions/collections_search.py +13 -3
  8. stac_fastapi_core-8.0.0/stac_fastapi/core/extensions/filter.py +295 -0
  9. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/models/links.py +17 -0
  10. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/serializers.py +50 -3
  11. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/utilities.py +29 -2
  12. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/validate.py +45 -26
  13. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/version.py +1 -1
  14. stac_fastapi_core-7.2.0/stac_fastapi/core/extensions/filter.py +0 -135
  15. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/.gitignore +0 -0
  16. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/README.md +0 -0
  17. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/pytest.ini +0 -0
  18. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/__init__.py +0 -0
  19. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/base_settings.py +0 -0
  20. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/basic_auth.py +0 -0
  21. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/datetime_utils.py +0 -0
  22. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/exceptions.py +0 -0
  23. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/extensions/__init__.py +0 -0
  24. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/extensions/aggregation.py +0 -0
  25. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/extensions/fields.py +0 -0
  26. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/extensions/query.py +0 -0
  27. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/models/__init__.py +0 -0
  28. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/models/search.py +0 -0
  29. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/queryables.py +0 -0
  30. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/rate_limit.py +0 -0
  31. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/redis_utils.py +0 -0
  32. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/route_dependencies.py +0 -0
  33. {stac_fastapi_core-7.2.0 → stac_fastapi_core-8.0.0}/stac_fastapi/core/session.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stac_fastapi_core
3
- Version: 7.2.0
3
+ Version: 8.0.0
4
4
  Summary: Core library for the Elasticsearch and Opensearch stac-fastapi backends.
5
5
  Project-URL: Homepage, https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
6
6
  License: MIT
@@ -15,29 +15,29 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Classifier: Programming Language :: Python :: 3.14
16
16
  Requires-Python: >=3.11
17
17
  Requires-Dist: attrs>=23.2.0
18
- Requires-Dist: fastapi<0.142,>=0.109
18
+ Requires-Dist: cql2<0.7.0,>=0.6.0
19
+ Requires-Dist: fastapi<0.144,>=0.109
19
20
  Requires-Dist: jsonschema~=4.26.0
20
- Requires-Dist: orjson<3.13,>=3.11
21
+ Requires-Dist: orjson<3.14,>=3.11
21
22
  Requires-Dist: overrides<7.8,>=7.4
22
23
  Requires-Dist: pydantic<3.0.0,>=2.4.1
23
- Requires-Dist: pygeofilter<0.5.0,>=0.3.1
24
24
  Requires-Dist: slowapi~=0.1.9
25
25
  Requires-Dist: stac-fastapi-api==7.0.0
26
26
  Requires-Dist: stac-fastapi-extensions==7.0.0
27
27
  Requires-Dist: stac-fastapi-types==7.0.0
28
- Requires-Dist: stac-pydantic<3.7,>=3.3
29
- Requires-Dist: starlette<1.7.0,>=0.35.0
30
- Requires-Dist: uvicorn<0.53,>=0.23
28
+ Requires-Dist: stac-pydantic<3.8,>=3.7
29
+ Requires-Dist: starlette<1.9.0,>=0.35.0
30
+ Requires-Dist: uvicorn<0.55,>=0.23
31
31
  Provides-Extra: catalogs
32
32
  Requires-Dist: stac-fastapi-catalogs-extension==1.0.0; extra == 'catalogs'
33
33
  Provides-Extra: redis
34
34
  Requires-Dist: redis<8.2,>=6.4; extra == 'redis'
35
35
  Requires-Dist: retry~=0.9.2; extra == 'redis'
36
36
  Provides-Extra: sentry
37
- Requires-Dist: sentry-sdk<2.70,>=2.49; extra == 'sentry'
37
+ Requires-Dist: sentry-sdk<2.73,>=2.49; extra == 'sentry'
38
38
  Provides-Extra: validator
39
39
  Requires-Dist: jsonpatch; extra == 'validator'
40
- Requires-Dist: stac-valid<4.6.0,>=4.2.2; extra == 'validator'
40
+ Requires-Dist: stac-valid<4.7,>=4.6.1; extra == 'validator'
41
41
  Description-Content-Type: text/markdown
42
42
 
43
43
  # stac-fastapi-core
@@ -29,20 +29,20 @@ keywords = [
29
29
  ]
30
30
  dynamic = ["version"]
31
31
  dependencies = [
32
- "fastapi>=0.109,<0.142",
32
+ "fastapi>=0.109,<0.144",
33
33
  "attrs>=23.2.0",
34
34
  "pydantic>=2.4.1,<3.0.0",
35
- "stac_pydantic>=3.3,<3.7",
35
+ "stac_pydantic>=3.7,<3.8",
36
36
  "stac-fastapi.types==7.0.0",
37
37
  "stac-fastapi.api==7.0.0",
38
38
  "stac-fastapi.extensions==7.0.0",
39
- "orjson>=3.11,<3.13",
39
+ "orjson>=3.11,<3.14",
40
40
  "overrides>=7.4,<7.8",
41
- "pygeofilter>=0.3.1,<0.5.0",
41
+ "cql2>=0.6.0,<0.7.0",
42
42
  "jsonschema~=4.26.0",
43
43
  "slowapi~=0.1.9",
44
- "uvicorn>=0.23,<0.53",
45
- "starlette>=0.35.0,<1.7.0",
44
+ "starlette>=0.35.0,<1.9.0",
45
+ "uvicorn>=0.23,<0.55",
46
46
  ]
47
47
 
48
48
  [project.optional-dependencies]
@@ -51,13 +51,13 @@ redis = [
51
51
  "retry~=0.9.2",
52
52
  ]
53
53
  sentry = [
54
- "sentry-sdk>=2.49,<2.70",
54
+ "sentry-sdk>=2.49,<2.73",
55
55
  ]
56
56
  catalogs = [
57
57
  "stac-fastapi-catalogs-extension==1.0.0",
58
58
  ]
59
59
  validator = [
60
- "stac-valid>=4.2.2,<4.6.0",
60
+ "stac-valid>=4.6.1,<4.7",
61
61
  "jsonpatch"
62
62
  ]
63
63
 
@@ -5,6 +5,17 @@ from typing import Any, Iterable
5
5
 
6
6
  from stac_pydantic.shared import BBox
7
7
 
8
+ from stac_fastapi.core.etag import DocumentVersion
9
+ from stac_fastapi.sfeos_helpers.database import (
10
+ find_item_in_alias,
11
+ index_alias_by_collection_id,
12
+ mk_item_id,
13
+ )
14
+ from stac_fastapi.sfeos_helpers.search_engine import (
15
+ BaseIndexInserter,
16
+ DatetimeIndexInserter,
17
+ )
18
+ from stac_fastapi.types.errors import NotFoundError
8
19
  from stac_fastapi.types.stac import Collection, Item
9
20
 
10
21
 
@@ -16,6 +27,12 @@ class BaseDatabaseLogic(abc.ABC):
16
27
  Subclasses must provide implementations for these methods.
17
28
  """
18
29
 
30
+ client: Any
31
+ async_index_inserter: BaseIndexInserter
32
+ #: Search-engine exception raised when a document lookup misses. Concrete
33
+ #: backends set this to their engine-specific not-found error class.
34
+ not_found_error_class: type[Exception]
35
+
19
36
  @abc.abstractmethod
20
37
  async def get_all_collections(
21
38
  self,
@@ -48,13 +65,85 @@ class BaseDatabaseLogic(abc.ABC):
48
65
  pass
49
66
 
50
67
  @abc.abstractmethod
51
- async def get_one_item(self, collection_id: str, item_id: str) -> dict:
68
+ async def get_one_item(
69
+ self, collection_id: str, item_id: str, *, include_hidden: bool = False
70
+ ) -> dict:
52
71
  """Retrieve a single item from the database."""
53
72
  pass
54
73
 
74
+ async def get_item_for_write(self, collection_id: str, item_id: str) -> dict:
75
+ """Retrieve an item for a write without applying read visibility filters.
76
+
77
+ Under datetime index filtering the real-time ``mget`` lookup is used
78
+ for the same reason as in :meth:`get_item_hit_for_write`.
79
+ """
80
+ if isinstance(self.async_index_inserter, DatetimeIndexInserter):
81
+ hit = await self.get_item_hit_for_write(collection_id, item_id)
82
+ return hit["_source"]
83
+ return await self.get_one_item(collection_id, item_id, include_hidden=True)
84
+
85
+ async def get_item_hit_for_write(self, collection_id: str, item_id: str) -> dict:
86
+ """Retrieve the write-lookup hit for an item, including its version.
87
+
88
+ Returns a hit dict carrying ``_source``, ``_index``, ``_seq_no`` and
89
+ ``_primary_term``, so callers that then write via ``create_item`` can
90
+ pass the result back as ``existing_hit`` (the existence check is not
91
+ repeated) or read the stored version from it.
92
+
93
+ Under datetime index filtering, ``_id`` uniqueness is enforced per
94
+ physical index and a just-written document may not be visible to
95
+ ``_search`` yet, so the lookup is a real-time ``mget`` over the
96
+ collection alias's concrete indexes instead.
97
+
98
+ Raises:
99
+ NotFoundError: If the item does not exist in the collection.
100
+ """
101
+ if isinstance(self.async_index_inserter, DatetimeIndexInserter):
102
+ try:
103
+ hit = await find_item_in_alias(
104
+ self.client,
105
+ index_alias_by_collection_id(collection_id),
106
+ mk_item_id(item_id, collection_id),
107
+ )
108
+ except self.not_found_error_class:
109
+ hit = None
110
+ if hit is None:
111
+ raise NotFoundError(
112
+ f"Item {item_id} does not exist inside Collection {collection_id}"
113
+ )
114
+ return hit
115
+ item, version = await self.get_one_item_with_version(
116
+ collection_id, item_id, include_hidden=True
117
+ )
118
+ return {
119
+ "_source": item,
120
+ "_index": version.index,
121
+ "_seq_no": version.seq_no,
122
+ "_primary_term": version.primary_term,
123
+ }
124
+
125
+ @abc.abstractmethod
126
+ async def get_one_item_with_version(
127
+ self, collection_id: str, item_id: str, *, include_hidden: bool = False
128
+ ) -> tuple[dict, DocumentVersion]:
129
+ """Retrieve a single item and the version of its stored document.
130
+
131
+ With `include_hidden`, HIDE_ITEM_PATH does not apply, as for a write.
132
+ """
133
+ pass
134
+
55
135
  @abc.abstractmethod
56
- async def create_item(self, item: dict, refresh: bool = False) -> None:
57
- """Create an item in the database."""
136
+ async def create_item(
137
+ self,
138
+ item: dict,
139
+ refresh: bool = False,
140
+ version: DocumentVersion | None = None,
141
+ **kwargs: Any,
142
+ ) -> DocumentVersion | None:
143
+ """Create an item in the database; return the version it stored.
144
+
145
+ With `version` (If-Match), only that stored version is replaced; 412 otherwise.
146
+ """
58
147
  pass
59
148
 
60
149
  @abc.abstractmethod
@@ -72,6 +161,7 @@ class BaseDatabaseLogic(abc.ABC):
72
161
  item: dict,
73
162
  base_url: str,
74
163
  refresh: bool = True,
164
+ version: DocumentVersion | None = None,
75
165
  ) -> dict:
76
166
  """Patch a item in the database follows RF7396."""
77
167
  pass
@@ -85,13 +175,18 @@ class BaseDatabaseLogic(abc.ABC):
85
175
  base_url: str,
86
176
  create_nest: bool = False,
87
177
  refresh: bool = True,
178
+ version: DocumentVersion | None = None,
88
179
  ) -> dict:
89
180
  """Patch a item in the database follows RF6902."""
90
181
  pass
91
182
 
92
183
  @abc.abstractmethod
93
184
  async def delete_item(
94
- self, item_id: str, collection_id: str, refresh: bool = False
185
+ self,
186
+ item_id: str,
187
+ collection_id: str,
188
+ refresh: bool = False,
189
+ version: DocumentVersion | None = None,
95
190
  ) -> None:
96
191
  """Delete an item from the database."""
97
192
  pass
@@ -109,8 +204,10 @@ class BaseDatabaseLogic(abc.ABC):
109
204
  pass
110
205
 
111
206
  @abc.abstractmethod
112
- async def create_collection(self, collection: dict, refresh: bool = False) -> None:
113
- """Create a collection in the database."""
207
+ async def create_collection(
208
+ self, collection: dict, refresh: bool = False
209
+ ) -> DocumentVersion | None:
210
+ """Create a collection in the database; return the version it stored."""
114
211
  pass
115
212
 
116
213
  @abc.abstractmethod
@@ -120,6 +217,7 @@ class BaseDatabaseLogic(abc.ABC):
120
217
  collection: dict,
121
218
  base_url: str,
122
219
  refresh: bool = True,
220
+ version: DocumentVersion | None = None,
123
221
  ) -> dict:
124
222
  """Patch a collection in the database follows RF7396."""
125
223
  pass
@@ -132,6 +230,7 @@ class BaseDatabaseLogic(abc.ABC):
132
230
  base_url: str,
133
231
  create_nest: bool = False,
134
232
  refresh: bool = True,
233
+ version: DocumentVersion | None = None,
135
234
  ) -> dict:
136
235
  """Patch a collection in the database follows RF6902."""
137
236
  pass
@@ -141,16 +240,47 @@ class BaseDatabaseLogic(abc.ABC):
141
240
  """Find a collection in the database."""
142
241
  pass
143
242
 
243
+ @abc.abstractmethod
244
+ async def find_collection_with_version(
245
+ self, collection_id: str
246
+ ) -> tuple[dict, DocumentVersion]:
247
+ """Find a collection and the version of its stored document."""
248
+ pass
249
+
144
250
  @abc.abstractmethod
145
251
  async def update_collection(
146
- self, collection_id: str, collection: Collection, **kwargs: Any
147
- ) -> None:
252
+ self,
253
+ collection_id: str,
254
+ collection: Collection,
255
+ preserve_parent_ids: bool = False,
256
+ version: DocumentVersion | None = None,
257
+ **kwargs: Any,
258
+ ) -> DocumentVersion | None:
148
259
  """Update a collection in the database."""
149
260
  pass
150
261
 
262
+ @abc.abstractmethod
263
+ async def update_parent_ids(
264
+ self,
265
+ doc_id: str,
266
+ doc_type: str,
267
+ parent_id: str,
268
+ add: bool,
269
+ refresh: bool | str = False,
270
+ ) -> dict:
271
+ """Atomically add or remove one parent id in a Collection's or Catalog's parent_ids.
272
+
273
+ Returns:
274
+ The stored document after the update.
275
+ """
276
+ pass
277
+
151
278
  @abc.abstractmethod
152
279
  async def delete_collection(
153
- self, collection_id: str, refresh: bool = False
280
+ self,
281
+ collection_id: str,
282
+ refresh: bool = False,
283
+ version: DocumentVersion | None = None,
154
284
  ) -> None:
155
285
  """Delete a collection from the database."""
156
286
  pass
@@ -175,6 +305,7 @@ class BaseDatabaseLogic(abc.ABC):
175
305
  limit: int,
176
306
  request: Any = None,
177
307
  sort: list[dict[str, Any]] | None = None,
308
+ filter: dict[str, Any] | None = None,
178
309
  ) -> tuple[list[dict[str, Any]], str | None, int | None]:
179
310
  """Retrieve a list of catalogs from the database, supporting pagination.
180
311
 
@@ -183,6 +314,7 @@ class BaseDatabaseLogic(abc.ABC):
183
314
  limit (int): The number of results to return.
184
315
  request (Any, optional): The FastAPI request object. Defaults to None.
185
316
  sort (Optional[List[Dict[str, Any]]], optional): Optional sort parameter. Defaults to None.
317
+ filter (Optional[Dict[str, Any]], optional): A CQL2 JSON filter the catalogs must match. Defaults to None.
186
318
 
187
319
  Returns:
188
320
  A tuple of (catalogs, next pagination token if any, optional count).
@@ -190,8 +322,13 @@ class BaseDatabaseLogic(abc.ABC):
190
322
  pass
191
323
 
192
324
  @abc.abstractmethod
193
- async def create_catalog(self, catalog: dict, refresh: bool = False) -> None:
194
- """Create a catalog in the database."""
325
+ async def create_catalog(
326
+ self, catalog: dict, refresh: bool = False
327
+ ) -> DocumentVersion | None:
328
+ """Create a catalog; reject an id already used by any document.
329
+
330
+ Returns the version of the stored document.
331
+ """
195
332
  pass
196
333
 
197
334
  @abc.abstractmethod
@@ -200,12 +337,34 @@ class BaseDatabaseLogic(abc.ABC):
200
337
  pass
201
338
 
202
339
  @abc.abstractmethod
203
- async def delete_catalog(self, catalog_id: str, refresh: bool = False) -> None:
340
+ async def find_catalog_with_version(
341
+ self, catalog_id: str
342
+ ) -> tuple[dict, DocumentVersion]:
343
+ """Find a catalog and the version of its stored document."""
344
+ pass
345
+
346
+ @abc.abstractmethod
347
+ async def delete_catalog(
348
+ self,
349
+ catalog_id: str,
350
+ refresh: bool = False,
351
+ version: DocumentVersion | None = None,
352
+ ) -> None:
204
353
  """Delete a catalog from the database.
205
354
 
206
355
  Implementations MUST verify the document exists and is a Catalog
207
356
  before deleting, raising NotFoundError otherwise; deleting a catalog
208
- must never remove Collection or Item data.
357
+ must never remove Collection or Item data. With `version` (If-Match),
358
+ only that stored version is deleted, and nothing changes otherwise.
359
+ """
360
+ pass
361
+
362
+ @abc.abstractmethod
363
+ async def unlink_catalog_orphans(self, catalog_id: str) -> None:
364
+ """Unlink children that still reference a catalog that no longer exists.
365
+
366
+ Nothing is deleted. A retry of a catalog delete whose cleanup failed
367
+ calls this, with or without If-Match.
209
368
  """
210
369
  pass
211
370
 
@@ -217,8 +376,9 @@ class BaseDatabaseLogic(abc.ABC):
217
376
  token: str | None,
218
377
  request: Any = None,
219
378
  resource_type: str | None = None,
379
+ filter: dict[str, Any] | None = None,
220
380
  ) -> tuple[list[dict[str, Any]], int, str | None]:
221
- """Get children of a catalog.
381
+ """Get children of a catalog, optionally matching a CQL2 JSON filter.
222
382
 
223
383
  Returns:
224
384
  Tuple of (children_list, total_count, next_token).
@@ -232,9 +392,15 @@ class BaseDatabaseLogic(abc.ABC):
232
392
  limit: int,
233
393
  token: str | None,
234
394
  request: Any = None,
395
+ filter: dict[str, Any] | None = None,
396
+ sort: list[dict[str, Any]] | None = None,
397
+ q: list[str] | None = None,
235
398
  ) -> tuple[list[dict[str, Any]], int, str | None]:
236
399
  """Get collections of a catalog.
237
400
 
401
+ Optionally matching a CQL2 JSON `filter`, ordered by `sort`
402
+ ({field, direction} pairs), and matching `q` free-text terms.
403
+
238
404
  Returns:
239
405
  Tuple of (collections_list, total_count, next_token).
240
406
  """
@@ -247,8 +413,9 @@ class BaseDatabaseLogic(abc.ABC):
247
413
  limit: int,
248
414
  token: str | None,
249
415
  request: Any = None,
416
+ filter: dict[str, Any] | None = None,
250
417
  ) -> tuple[list[dict[str, Any]], int, str | None]:
251
- """Get sub-catalogs of a catalog.
418
+ """Get sub-catalogs of a catalog, optionally matching a CQL2 JSON filter.
252
419
 
253
420
  Returns:
254
421
  Tuple of (catalogs_list, total_count, next_token).
@@ -295,34 +462,26 @@ class BaseDatabaseLogic(abc.ABC):
295
462
  datetime: str | None = None,
296
463
  limit: int = 10,
297
464
  sortby: str | None = None,
298
- filter_expr: str | None = None,
299
- filter_lang: str | None = None,
465
+ filter: dict[str, Any] | None = None,
300
466
  token: str | None = None,
301
467
  query: str | None = None,
302
468
  fields: list[str] | None = None,
303
469
  ) -> Any:
304
- """Get items from a collection in a catalog."""
305
- pass
306
-
307
- @abc.abstractmethod
308
- async def get_catalog_collection_item(
309
- self,
310
- catalog_id: str,
311
- collection_id: str,
312
- item_id: str,
313
- request: Any,
314
- ) -> Any:
315
- """Get an item from a collection in a catalog."""
470
+ """Get items from a collection in a catalog, optionally matching a CQL2 JSON filter."""
316
471
  pass
317
472
 
318
473
  @abc.abstractmethod
319
474
  async def update_catalog(
320
475
  self,
321
476
  catalog_id: str,
322
- catalog: Any,
323
- request: Any,
324
- ) -> Any:
325
- """Update a catalog."""
477
+ catalog: dict,
478
+ refresh: bool | str = False,
479
+ version: DocumentVersion | None = None,
480
+ ) -> DocumentVersion | None:
481
+ """Replace a catalog, keeping its stored parent_ids; return the version stored.
482
+
483
+ With `version` (If-Match), only that stored version is replaced; 412 otherwise.
484
+ """
326
485
  pass
327
486
 
328
487
  @abc.abstractmethod
@@ -388,7 +547,9 @@ class BaseDatabaseLogic(abc.ABC):
388
547
  pass
389
548
 
390
549
  @abc.abstractmethod
391
- def populate_sort(self, sortby: list) -> Any:
550
+ def populate_sort(
551
+ self, sortby: list, field_types: dict[str, str] | None = None
552
+ ) -> Any:
392
553
  """Populate sort for the search."""
393
554
  pass
394
555
 
@@ -464,6 +625,11 @@ class BaseDatabaseLogic(abc.ABC):
464
625
  """Check if a collection exists."""
465
626
  pass
466
627
 
628
+ @abc.abstractmethod
629
+ def check_collection_exists_sync(self, collection_id: str) -> None:
630
+ """Check if a collection exists, using the synchronous client."""
631
+ pass
632
+
467
633
  @abc.abstractmethod
468
634
  async def delete_items(self) -> None:
469
635
  """Delete all items."""