sfeos-helpers 6.2.0__tar.gz → 6.3.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 (40) hide show
  1. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/PKG-INFO +41 -21
  2. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/README.md +40 -20
  3. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/setup.py +1 -1
  4. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/sfeos_helpers.egg-info/PKG-INFO +41 -21
  5. sfeos_helpers-6.3.0/sfeos_helpers.egg-info/requires.txt +1 -0
  6. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/database/query.py +5 -2
  7. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/database/utils.py +75 -38
  8. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/mappings.py +4 -2
  9. sfeos_helpers-6.3.0/stac_fastapi/sfeos_helpers/models/patch.py +124 -0
  10. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/version.py +1 -1
  11. sfeos_helpers-6.2.0/sfeos_helpers.egg-info/requires.txt +0 -1
  12. sfeos_helpers-6.2.0/stac_fastapi/sfeos_helpers/models/patch.py +0 -166
  13. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/setup.cfg +0 -0
  14. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/sfeos_helpers.egg-info/SOURCES.txt +0 -0
  15. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/sfeos_helpers.egg-info/dependency_links.txt +0 -0
  16. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/sfeos_helpers.egg-info/not-zip-safe +0 -0
  17. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/sfeos_helpers.egg-info/top_level.txt +0 -0
  18. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/aggregation/__init__.py +0 -0
  19. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/aggregation/client.py +0 -0
  20. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/aggregation/format.py +0 -0
  21. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/database/__init__.py +0 -0
  22. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/database/datetime.py +0 -0
  23. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/database/document.py +0 -0
  24. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/database/index.py +0 -0
  25. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/database/mapping.py +0 -0
  26. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/filter/__init__.py +0 -0
  27. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/filter/client.py +0 -0
  28. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/filter/cql2.py +0 -0
  29. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/filter/transform.py +0 -0
  30. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/search_engine/__init__.py +0 -0
  31. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/search_engine/base.py +0 -0
  32. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/search_engine/factory.py +0 -0
  33. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/search_engine/index_operations.py +0 -0
  34. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/search_engine/inserters.py +0 -0
  35. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/search_engine/managers.py +0 -0
  36. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/search_engine/selection/__init__.py +0 -0
  37. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/search_engine/selection/base.py +0 -0
  38. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/search_engine/selection/cache_manager.py +0 -0
  39. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/search_engine/selection/factory.py +0 -0
  40. {sfeos_helpers-6.2.0 → sfeos_helpers-6.3.0}/stac_fastapi/sfeos_helpers/search_engine/selection/selectors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sfeos_helpers
3
- Version: 6.2.0
3
+ Version: 6.3.0
4
4
  Summary: Helper library for the Elasticsearch and Opensearch stac-fastapi backends.
5
5
  Home-page: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
6
6
  License: MIT
@@ -84,26 +84,43 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
84
84
 
85
85
  ## Table of Contents
86
86
 
87
- - [Documentation & Resources](#documentation--resources)
88
- - [Package Structure](#package-structure)
89
- - [Examples](#examples)
90
- - [Performance](#performance)
91
- - [Quick Start](#quick-start)
92
- - [Installation](#installation)
93
- - [Running Locally](#running-locally)
94
- - [Configuration reference](#configuration-reference)
95
- - [Interacting with the API](#interacting-with-the-api)
96
- - [Configure the API](#configure-the-api)
97
- - [Collection pagination](#collection-pagination)
98
- - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
99
- - [Elasticsearch Mappings](#elasticsearch-mappings)
100
- - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
101
- - [Snapshots](#snapshots)
102
- - [Reindexing](#reindexing)
103
- - [Auth](#auth)
104
- - [Aggregation](#aggregation)
105
- - [Rate Limiting](#rate-limiting)
106
- - [Datetime-Based Index Management](#datetime-based-index-management)
87
+ - [stac-fastapi-elasticsearch-opensearch](#stac-fastapi-elasticsearch-opensearch)
88
+ - [Sponsors \& Supporters](#sponsors--supporters)
89
+ - [Project Introduction - What is SFEOS?](#project-introduction---what-is-sfeos)
90
+ - [Common Deployment Patterns](#common-deployment-patterns)
91
+ - [Technologies](#technologies)
92
+ - [Table of Contents](#table-of-contents)
93
+ - [Documentation \& Resources](#documentation--resources)
94
+ - [Package Structure](#package-structure)
95
+ - [Examples](#examples)
96
+ - [Performance](#performance)
97
+ - [Direct Response Mode](#direct-response-mode)
98
+ - [Quick Start](#quick-start)
99
+ - [Installation](#installation)
100
+ - [Running Locally](#running-locally)
101
+ - [Using Pre-built Docker Images](#using-pre-built-docker-images)
102
+ - [Using Docker Compose](#using-docker-compose)
103
+ - [Configuration Reference](#configuration-reference)
104
+ - [Datetime-Based Index Management](#datetime-based-index-management)
105
+ - [Overview](#overview)
106
+ - [When to Use](#when-to-use)
107
+ - [Configuration](#configuration)
108
+ - [Enabling Datetime-Based Indexing](#enabling-datetime-based-indexing)
109
+ - [Related Configuration Variables](#related-configuration-variables)
110
+ - [How Datetime-Based Indexing Works](#how-datetime-based-indexing-works)
111
+ - [Index and Alias Naming Convention](#index-and-alias-naming-convention)
112
+ - [Index Size Management](#index-size-management)
113
+ - [Interacting with the API](#interacting-with-the-api)
114
+ - [Configure the API](#configure-the-api)
115
+ - [Collection Pagination](#collection-pagination)
116
+ - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
117
+ - [Elasticsearch Mappings](#elasticsearch-mappings)
118
+ - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
119
+ - [Snapshots](#snapshots)
120
+ - [Reindexing](#reindexing)
121
+ - [Auth](#auth)
122
+ - [Aggregation](#aggregation)
123
+ - [Rate Limiting](#rate-limiting)
107
124
 
108
125
  ## Documentation & Resources
109
126
 
@@ -245,6 +262,9 @@ You can customize additional settings in your `.env` file:
245
262
  | `RAISE_ON_BULK_ERROR` | Controls whether bulk insert operations raise exceptions on errors. If set to `true`, the operation will stop and raise an exception when an error occurs. If set to `false`, errors will be logged, and the operation will continue. **Note:** STAC Item and ItemCollection validation errors will always raise, regardless of this flag. | `false` | Optional |
246
263
  | `DATABASE_REFRESH` | Controls whether database operations refresh the index immediately after changes. If set to `true`, changes will be immediately searchable. If set to `false`, changes may not be immediately visible but can improve performance for bulk operations. If set to `wait_for`, changes will wait for the next refresh cycle to become visible. | `false` | Optional |
247
264
  | `ENABLE_TRANSACTIONS_EXTENSIONS` | Enables or disables the Transactions and Bulk Transactions API extensions. If set to `false`, the POST `/collections` route and related transaction endpoints (including bulk transaction operations) will be unavailable in the API. This is useful for deployments where mutating the catalog via the API should be prevented. | `true` | Optional |
265
+ | `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional |
266
+ | `STAC_INDEX_ASSETS` | Controls if Assets are indexed when added to Elasticsearch/Opensearch. This allows asset fields to be included in search queries. | `false` | Optional |
267
+ | `ENV_MAX_LIMIT` | Configures the environment variable in SFEOS to override the default `MAX_LIMIT`, which controls the limit parameter for returned items and STAC collections. | `10,000` | Optional |
248
268
 
249
269
  > [!NOTE]
250
270
  > The variables `ES_HOST`, `ES_PORT`, `ES_USE_SSL`, `ES_VERIFY_CERTS` and `ES_TIMEOUT` apply to both Elasticsearch and OpenSearch backends, so there is no need to rename the key names to `OS_` even if you're using OpenSearch.
@@ -66,26 +66,43 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
66
66
 
67
67
  ## Table of Contents
68
68
 
69
- - [Documentation & Resources](#documentation--resources)
70
- - [Package Structure](#package-structure)
71
- - [Examples](#examples)
72
- - [Performance](#performance)
73
- - [Quick Start](#quick-start)
74
- - [Installation](#installation)
75
- - [Running Locally](#running-locally)
76
- - [Configuration reference](#configuration-reference)
77
- - [Interacting with the API](#interacting-with-the-api)
78
- - [Configure the API](#configure-the-api)
79
- - [Collection pagination](#collection-pagination)
80
- - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
81
- - [Elasticsearch Mappings](#elasticsearch-mappings)
82
- - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
83
- - [Snapshots](#snapshots)
84
- - [Reindexing](#reindexing)
85
- - [Auth](#auth)
86
- - [Aggregation](#aggregation)
87
- - [Rate Limiting](#rate-limiting)
88
- - [Datetime-Based Index Management](#datetime-based-index-management)
69
+ - [stac-fastapi-elasticsearch-opensearch](#stac-fastapi-elasticsearch-opensearch)
70
+ - [Sponsors \& Supporters](#sponsors--supporters)
71
+ - [Project Introduction - What is SFEOS?](#project-introduction---what-is-sfeos)
72
+ - [Common Deployment Patterns](#common-deployment-patterns)
73
+ - [Technologies](#technologies)
74
+ - [Table of Contents](#table-of-contents)
75
+ - [Documentation \& Resources](#documentation--resources)
76
+ - [Package Structure](#package-structure)
77
+ - [Examples](#examples)
78
+ - [Performance](#performance)
79
+ - [Direct Response Mode](#direct-response-mode)
80
+ - [Quick Start](#quick-start)
81
+ - [Installation](#installation)
82
+ - [Running Locally](#running-locally)
83
+ - [Using Pre-built Docker Images](#using-pre-built-docker-images)
84
+ - [Using Docker Compose](#using-docker-compose)
85
+ - [Configuration Reference](#configuration-reference)
86
+ - [Datetime-Based Index Management](#datetime-based-index-management)
87
+ - [Overview](#overview)
88
+ - [When to Use](#when-to-use)
89
+ - [Configuration](#configuration)
90
+ - [Enabling Datetime-Based Indexing](#enabling-datetime-based-indexing)
91
+ - [Related Configuration Variables](#related-configuration-variables)
92
+ - [How Datetime-Based Indexing Works](#how-datetime-based-indexing-works)
93
+ - [Index and Alias Naming Convention](#index-and-alias-naming-convention)
94
+ - [Index Size Management](#index-size-management)
95
+ - [Interacting with the API](#interacting-with-the-api)
96
+ - [Configure the API](#configure-the-api)
97
+ - [Collection Pagination](#collection-pagination)
98
+ - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
99
+ - [Elasticsearch Mappings](#elasticsearch-mappings)
100
+ - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
101
+ - [Snapshots](#snapshots)
102
+ - [Reindexing](#reindexing)
103
+ - [Auth](#auth)
104
+ - [Aggregation](#aggregation)
105
+ - [Rate Limiting](#rate-limiting)
89
106
 
90
107
  ## Documentation & Resources
91
108
 
@@ -227,6 +244,9 @@ You can customize additional settings in your `.env` file:
227
244
  | `RAISE_ON_BULK_ERROR` | Controls whether bulk insert operations raise exceptions on errors. If set to `true`, the operation will stop and raise an exception when an error occurs. If set to `false`, errors will be logged, and the operation will continue. **Note:** STAC Item and ItemCollection validation errors will always raise, regardless of this flag. | `false` | Optional |
228
245
  | `DATABASE_REFRESH` | Controls whether database operations refresh the index immediately after changes. If set to `true`, changes will be immediately searchable. If set to `false`, changes may not be immediately visible but can improve performance for bulk operations. If set to `wait_for`, changes will wait for the next refresh cycle to become visible. | `false` | Optional |
229
246
  | `ENABLE_TRANSACTIONS_EXTENSIONS` | Enables or disables the Transactions and Bulk Transactions API extensions. If set to `false`, the POST `/collections` route and related transaction endpoints (including bulk transaction operations) will be unavailable in the API. This is useful for deployments where mutating the catalog via the API should be prevented. | `true` | Optional |
247
+ | `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional |
248
+ | `STAC_INDEX_ASSETS` | Controls if Assets are indexed when added to Elasticsearch/Opensearch. This allows asset fields to be included in search queries. | `false` | Optional |
249
+ | `ENV_MAX_LIMIT` | Configures the environment variable in SFEOS to override the default `MAX_LIMIT`, which controls the limit parameter for returned items and STAC collections. | `10,000` | Optional |
230
250
 
231
251
  > [!NOTE]
232
252
  > The variables `ES_HOST`, `ES_PORT`, `ES_USE_SSL`, `ES_VERIFY_CERTS` and `ES_TIMEOUT` apply to both Elasticsearch and OpenSearch backends, so there is no need to rename the key names to `OS_` even if you're using OpenSearch.
@@ -6,7 +6,7 @@ with open("README.md") as f:
6
6
  desc = f.read()
7
7
 
8
8
  install_requires = [
9
- "stac-fastapi.core==6.2.0",
9
+ "stac-fastapi.core==6.3.0",
10
10
  ]
11
11
 
12
12
  setup(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sfeos-helpers
3
- Version: 6.2.0
3
+ Version: 6.3.0
4
4
  Summary: Helper library for the Elasticsearch and Opensearch stac-fastapi backends.
5
5
  Home-page: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
6
6
  License: MIT
@@ -84,26 +84,43 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
84
84
 
85
85
  ## Table of Contents
86
86
 
87
- - [Documentation & Resources](#documentation--resources)
88
- - [Package Structure](#package-structure)
89
- - [Examples](#examples)
90
- - [Performance](#performance)
91
- - [Quick Start](#quick-start)
92
- - [Installation](#installation)
93
- - [Running Locally](#running-locally)
94
- - [Configuration reference](#configuration-reference)
95
- - [Interacting with the API](#interacting-with-the-api)
96
- - [Configure the API](#configure-the-api)
97
- - [Collection pagination](#collection-pagination)
98
- - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
99
- - [Elasticsearch Mappings](#elasticsearch-mappings)
100
- - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
101
- - [Snapshots](#snapshots)
102
- - [Reindexing](#reindexing)
103
- - [Auth](#auth)
104
- - [Aggregation](#aggregation)
105
- - [Rate Limiting](#rate-limiting)
106
- - [Datetime-Based Index Management](#datetime-based-index-management)
87
+ - [stac-fastapi-elasticsearch-opensearch](#stac-fastapi-elasticsearch-opensearch)
88
+ - [Sponsors \& Supporters](#sponsors--supporters)
89
+ - [Project Introduction - What is SFEOS?](#project-introduction---what-is-sfeos)
90
+ - [Common Deployment Patterns](#common-deployment-patterns)
91
+ - [Technologies](#technologies)
92
+ - [Table of Contents](#table-of-contents)
93
+ - [Documentation \& Resources](#documentation--resources)
94
+ - [Package Structure](#package-structure)
95
+ - [Examples](#examples)
96
+ - [Performance](#performance)
97
+ - [Direct Response Mode](#direct-response-mode)
98
+ - [Quick Start](#quick-start)
99
+ - [Installation](#installation)
100
+ - [Running Locally](#running-locally)
101
+ - [Using Pre-built Docker Images](#using-pre-built-docker-images)
102
+ - [Using Docker Compose](#using-docker-compose)
103
+ - [Configuration Reference](#configuration-reference)
104
+ - [Datetime-Based Index Management](#datetime-based-index-management)
105
+ - [Overview](#overview)
106
+ - [When to Use](#when-to-use)
107
+ - [Configuration](#configuration)
108
+ - [Enabling Datetime-Based Indexing](#enabling-datetime-based-indexing)
109
+ - [Related Configuration Variables](#related-configuration-variables)
110
+ - [How Datetime-Based Indexing Works](#how-datetime-based-indexing-works)
111
+ - [Index and Alias Naming Convention](#index-and-alias-naming-convention)
112
+ - [Index Size Management](#index-size-management)
113
+ - [Interacting with the API](#interacting-with-the-api)
114
+ - [Configure the API](#configure-the-api)
115
+ - [Collection Pagination](#collection-pagination)
116
+ - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
117
+ - [Elasticsearch Mappings](#elasticsearch-mappings)
118
+ - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
119
+ - [Snapshots](#snapshots)
120
+ - [Reindexing](#reindexing)
121
+ - [Auth](#auth)
122
+ - [Aggregation](#aggregation)
123
+ - [Rate Limiting](#rate-limiting)
107
124
 
108
125
  ## Documentation & Resources
109
126
 
@@ -245,6 +262,9 @@ You can customize additional settings in your `.env` file:
245
262
  | `RAISE_ON_BULK_ERROR` | Controls whether bulk insert operations raise exceptions on errors. If set to `true`, the operation will stop and raise an exception when an error occurs. If set to `false`, errors will be logged, and the operation will continue. **Note:** STAC Item and ItemCollection validation errors will always raise, regardless of this flag. | `false` | Optional |
246
263
  | `DATABASE_REFRESH` | Controls whether database operations refresh the index immediately after changes. If set to `true`, changes will be immediately searchable. If set to `false`, changes may not be immediately visible but can improve performance for bulk operations. If set to `wait_for`, changes will wait for the next refresh cycle to become visible. | `false` | Optional |
247
264
  | `ENABLE_TRANSACTIONS_EXTENSIONS` | Enables or disables the Transactions and Bulk Transactions API extensions. If set to `false`, the POST `/collections` route and related transaction endpoints (including bulk transaction operations) will be unavailable in the API. This is useful for deployments where mutating the catalog via the API should be prevented. | `true` | Optional |
265
+ | `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional |
266
+ | `STAC_INDEX_ASSETS` | Controls if Assets are indexed when added to Elasticsearch/Opensearch. This allows asset fields to be included in search queries. | `false` | Optional |
267
+ | `ENV_MAX_LIMIT` | Configures the environment variable in SFEOS to override the default `MAX_LIMIT`, which controls the limit parameter for returned items and STAC collections. | `10,000` | Optional |
248
268
 
249
269
  > [!NOTE]
250
270
  > The variables `ES_HOST`, `ES_PORT`, `ES_USE_SSL`, `ES_VERIFY_CERTS` and `ES_TIMEOUT` apply to both Elasticsearch and OpenSearch backends, so there is no need to rename the key names to `OS_` even if you're using OpenSearch.
@@ -0,0 +1 @@
1
+ stac-fastapi.core==6.3.0
@@ -80,11 +80,14 @@ def populate_sort_shared(sortby: List) -> Optional[Dict[str, Dict[str, str]]]:
80
80
  This function transforms a list of sort specifications into the format required by
81
81
  Elasticsearch/OpenSearch for sorting query results. The returned dictionary can be
82
82
  directly used in search requests.
83
+ Always includes 'id' as secondary sort to ensure unique pagination tokens.
83
84
  """
84
85
  if sortby:
85
- return {s.field: {"order": s.direction} for s in sortby}
86
+ sort_config = {s.field: {"order": s.direction} for s in sortby}
87
+ sort_config.setdefault("id", {"order": "asc"})
88
+ return sort_config
86
89
  else:
87
- return None
90
+ return {"id": {"order": "asc"}}
88
91
 
89
92
 
90
93
  def add_collections_to_body(
@@ -76,7 +76,7 @@ def merge_to_operations(data: Dict) -> List:
76
76
  nested_operations = merge_to_operations(value)
77
77
 
78
78
  for nested_operation in nested_operations:
79
- nested_operation.path = f"{key}.{nested_operation.path}"
79
+ nested_operation.path = f"{key}/{nested_operation.path}"
80
80
  operations.append(nested_operation)
81
81
 
82
82
  else:
@@ -90,6 +90,7 @@ def check_commands(
90
90
  op: str,
91
91
  path: ElasticPath,
92
92
  from_path: bool = False,
93
+ create_nest: bool = False,
93
94
  ) -> None:
94
95
  """Add Elasticsearch checks to operation.
95
96
 
@@ -101,25 +102,44 @@ def check_commands(
101
102
 
102
103
  """
103
104
  if path.nest:
104
- commands.add(
105
- f"if (!ctx._source.containsKey('{path.nest}'))"
106
- f"{{Debug.explain('{path.nest} does not exist');}}"
107
- )
108
-
109
- if path.index or op in ["remove", "replace", "test"] or from_path:
110
- commands.add(
111
- f"if (!ctx._source{path.es_nest}.containsKey('{path.key}'))"
112
- f"{{Debug.explain('{path.key} does not exist in {path.nest}');}}"
113
- )
114
-
115
- if from_path and path.index is not None:
116
- commands.add(
117
- f"if ((ctx._source{path.es_location} instanceof ArrayList"
118
- f" && ctx._source{path.es_location}.size() < {path.index})"
119
- f" || (!(ctx._source{path.es_location} instanceof ArrayList)"
120
- f" && !ctx._source{path.es_location}.containsKey('{path.index}')))"
121
- f"{{Debug.explain('{path.path} does not exist');}}"
122
- )
105
+ part_nest = ""
106
+ for index, path_part in enumerate(path.parts):
107
+
108
+ # Create nested dictionaries if not present for merge operations
109
+ if create_nest and not from_path:
110
+ value = "[:]"
111
+ for sub_part in reversed(path.parts[index + 1 :]):
112
+ value = f"['{sub_part}': {value}]"
113
+
114
+ commands.add(
115
+ f"if (!ctx._source{part_nest}.containsKey('{path_part}'))"
116
+ f"{{ctx._source{part_nest}['{path_part}'] = {value};}}"
117
+ f"{'' if index == len(path.parts) - 1 else' else '}" # noqa: E275
118
+ )
119
+
120
+ else:
121
+ commands.add(
122
+ f"if (!ctx._source{part_nest}.containsKey('{path_part}'))"
123
+ f"{{Debug.explain('{path_part} in {path.path} does not exist');}}"
124
+ )
125
+
126
+ part_nest += f"['{path_part}']"
127
+
128
+ if from_path or op in ["remove", "replace", "test"]:
129
+
130
+ if isinstance(path.key, int):
131
+ commands.add(
132
+ f"if ((ctx._source{path.es_nest} instanceof ArrayList"
133
+ f" && ctx._source{path.es_nest}.size() < {abs(path.key)})"
134
+ f" || (!(ctx._source{path.es_nest} instanceof ArrayList)"
135
+ f" && !ctx._source{path.es_nest}.containsKey('{path.key}')))"
136
+ f"{{Debug.explain('{path.key} does not exist in {path.nest}');}}" # noqa: E713
137
+ )
138
+ else:
139
+ commands.add(
140
+ f"if (!ctx._source{path.es_nest}.containsKey('{path.key}'))"
141
+ f"{{Debug.explain('{path.key} does not exist in {path.nest}');}}" # noqa: E713
142
+ )
123
143
 
124
144
 
125
145
  def remove_commands(commands: ESCommandSet, path: ElasticPath) -> None:
@@ -130,15 +150,16 @@ def remove_commands(commands: ESCommandSet, path: ElasticPath) -> None:
130
150
  path (ElasticPath): Path to value to be removed
131
151
 
132
152
  """
133
- if path.index is not None:
153
+ commands.add(f"def {path.variable_name};")
154
+ if isinstance(path.key, int):
134
155
  commands.add(
135
- f"def {path.variable_name} = ctx._source{path.es_location}.remove({path.index});"
156
+ f"if (ctx._source{path.es_nest} instanceof ArrayList)"
157
+ f"{{{path.variable_name} = ctx._source{path.es_nest}.remove({path.es_key});}} else "
136
158
  )
137
159
 
138
- else:
139
- commands.add(
140
- f"def {path.variable_name} = ctx._source{path.es_nest}.remove('{path.key}');"
141
- )
160
+ commands.add(
161
+ f"{path.variable_name} = ctx._source{path.es_nest}.remove('{path.key}');"
162
+ )
142
163
 
143
164
 
144
165
  def add_commands(
@@ -160,21 +181,22 @@ def add_commands(
160
181
  value = (
161
182
  from_path.variable_name
162
183
  if operation.op == "move"
163
- else f"ctx._source.{from_path.es_path}"
184
+ else f"ctx._source{from_path.es_path}"
164
185
  )
186
+
165
187
  else:
166
188
  value = f"params.{path.param_key}"
167
189
  params[path.param_key] = operation.value
168
190
 
169
- if path.index is not None:
191
+ if isinstance(path.key, int):
170
192
  commands.add(
171
- f"if (ctx._source{path.es_location} instanceof ArrayList)"
172
- f"{{ctx._source{path.es_location}.{'add' if operation.op in ['add', 'move'] else 'set'}({path.index}, {value})}}"
173
- f"else{{ctx._source.{path.es_path} = {value}}}"
193
+ f"if (ctx._source{path.es_nest} instanceof ArrayList)"
194
+ f"{{ctx._source{path.es_nest}.{'add' if operation.op in ['add', 'move'] else 'set'}({path.es_key}, {value});}}"
195
+ f" else ctx._source{path.es_nest}['{path.es_key}'] = {value};"
174
196
  )
175
197
 
176
198
  else:
177
- commands.add(f"ctx._source.{path.es_path} = {value};")
199
+ commands.add(f"ctx._source{path.es_path} = {value};")
178
200
 
179
201
 
180
202
  def test_commands(
@@ -190,14 +212,23 @@ def test_commands(
190
212
  value = f"params.{path.param_key}"
191
213
  params[path.param_key] = operation.value
192
214
 
215
+ if isinstance(path.key, int):
216
+ commands.add(
217
+ f"if (ctx._source{path.es_nest} instanceof ArrayList)"
218
+ f"{{if (ctx._source{path.es_nest}[{path.es_key}] != {value})"
219
+ f"{{Debug.explain('Test failed `{path.path}`"
220
+ f" != ' + ctx._source{path.es_path});}}"
221
+ f"}} else "
222
+ )
223
+
193
224
  commands.add(
194
- f"if (ctx._source.{path.es_path} != {value})"
195
- f"{{Debug.explain('Test failed `{path.path}` | "
196
- f"{operation.json_value} != ' + ctx._source.{path.es_path});}}"
225
+ f"if (ctx._source{path.es_path} != {value})"
226
+ f"{{Debug.explain('Test failed `{path.path}`"
227
+ f" != ' + ctx._source{path.es_path});}}"
197
228
  )
198
229
 
199
230
 
200
- def operations_to_script(operations: List) -> Dict:
231
+ def operations_to_script(operations: List, create_nest: bool = False) -> Dict:
201
232
  """Convert list of operation to painless script.
202
233
 
203
234
  Args:
@@ -215,10 +246,16 @@ def operations_to_script(operations: List) -> Dict:
215
246
  ElasticPath(path=operation.from_) if hasattr(operation, "from_") else None
216
247
  )
217
248
 
218
- check_commands(commands=commands, op=operation.op, path=path)
249
+ check_commands(
250
+ commands=commands, op=operation.op, path=path, create_nest=create_nest
251
+ )
219
252
  if from_path is not None:
220
253
  check_commands(
221
- commands=commands, op=operation.op, path=from_path, from_path=True
254
+ commands=commands,
255
+ op=operation.op,
256
+ path=from_path,
257
+ from_path=True,
258
+ create_nest=create_nest,
222
259
  )
223
260
 
224
261
  if operation.op in ["remove", "move"]:
@@ -28,6 +28,8 @@ Function Naming Conventions:
28
28
  import os
29
29
  from typing import Any, Dict, Literal, Protocol
30
30
 
31
+ from stac_fastapi.core.utilities import get_bool_env
32
+
31
33
 
32
34
  # stac_pydantic classes extend _GeometryBase, which doesn't have a type field,
33
35
  # So create our own Protocol for typing
@@ -134,7 +136,7 @@ ES_ITEMS_MAPPINGS = {
134
136
  "id": {"type": "keyword"},
135
137
  "collection": {"type": "keyword"},
136
138
  "geometry": {"type": "geo_shape"},
137
- "assets": {"type": "object", "enabled": False},
139
+ "assets": {"type": "object", "enabled": get_bool_env("STAC_INDEX_ASSETS")},
138
140
  "links": {"type": "object", "enabled": False},
139
141
  "properties": {
140
142
  "type": "object",
@@ -162,7 +164,7 @@ ES_COLLECTIONS_MAPPINGS = {
162
164
  "extent.temporal.interval": {"type": "date"},
163
165
  "providers": {"type": "object", "enabled": False},
164
166
  "links": {"type": "object", "enabled": False},
165
- "item_assets": {"type": "object", "enabled": False},
167
+ "item_assets": {"type": "object", "enabled": get_bool_env("STAC_INDEX_ASSETS")},
166
168
  },
167
169
  }
168
170
 
@@ -0,0 +1,124 @@
1
+ """patch helpers."""
2
+
3
+ import re
4
+ from typing import Any, Dict, Optional, Union
5
+
6
+ from pydantic import BaseModel, model_validator
7
+
8
+ regex = re.compile(r"([^.' ]*:[^.'[ ]*)\.?")
9
+ replacements = str.maketrans({"/": "", ".": "", ":": "", "[": "", "]": ""})
10
+
11
+
12
+ class ESCommandSet:
13
+ """Uses dictionary keys to behaviour of ordered set.
14
+
15
+ Yields:
16
+ str: Elasticsearch commands
17
+ """
18
+
19
+ dict_: Dict[str, None] = {}
20
+
21
+ def __init__(self):
22
+ """Initialise ESCommandSet instance."""
23
+ self.dict_ = {}
24
+
25
+ def add(self, value: str):
26
+ """Add command.
27
+
28
+ Args:
29
+ value (str): value to be added
30
+ """
31
+ self.dict_[value] = None
32
+
33
+ def remove(self, value: str):
34
+ """Remove command.
35
+
36
+ Args:
37
+ value (str): value to be removed
38
+ """
39
+ del self.dict_[value]
40
+
41
+ def __iter__(self):
42
+ """Iterate Elasticsearch commands.
43
+
44
+ Yields:
45
+ str: Elasticsearch command
46
+ """
47
+ yield from self.dict_.keys()
48
+
49
+
50
+ def to_es(string: str):
51
+ """Convert patch operation key to Elasticsearch key.
52
+
53
+ Args:
54
+ string (str): string to be converted
55
+
56
+ Returns:
57
+ _type_: converted string
58
+ """
59
+ if matches := regex.findall(string):
60
+ for match in set(matches):
61
+ string = re.sub(rf"\.?{match}", f"['{match}']", string)
62
+
63
+ return string
64
+
65
+
66
+ class ElasticPath(BaseModel):
67
+ """Converts a JSON path to an Elasticsearch path.
68
+
69
+ Args:
70
+ path (str): JSON path to be converted.
71
+
72
+ """
73
+
74
+ parts: list[str] = []
75
+
76
+ path: Optional[str] = None
77
+ key: Optional[Union[str, int]] = None
78
+ nest: Optional[str] = None
79
+
80
+ es_path: Optional[str] = None
81
+ es_key: Optional[str] = None
82
+ es_nest: Optional[str] = None
83
+
84
+ variable_name: Optional[str] = None
85
+ param_key: Optional[str] = None
86
+
87
+ class Config:
88
+ """Class config."""
89
+
90
+ frozen = True
91
+
92
+ @model_validator(mode="before")
93
+ @classmethod
94
+ def validate_model(cls, data: Any):
95
+ """Set optional fields from JSON path.
96
+
97
+ Args:
98
+ data (Any): input data
99
+ """
100
+ data["parts"] = data["path"].lstrip("/").split("/")
101
+
102
+ data["key"] = data["parts"].pop(-1)
103
+ data["nest"] = "/".join(data["parts"])
104
+ data["path"] = data["nest"] + "/" + data["key"]
105
+
106
+ data["es_key"] = data["key"]
107
+ data["es_nest"] = "".join([f"['{part}']" for part in data["parts"]])
108
+ data["es_path"] = data["es_nest"] + f"['{data['es_key']}']"
109
+
110
+ if data["key"].lstrip("-").isdigit() or data["key"] == "-":
111
+ data["key"] = -1 if data["key"] == "-" else int(data["key"])
112
+ data["es_key"] = (
113
+ f"ctx._source{data['es_nest']}.size() - {-data['key']}"
114
+ if data["key"] < 0
115
+ else str(data["key"])
116
+ )
117
+ data["es_path"] = data["es_nest"] + f"[{data['es_key']}]"
118
+
119
+ data[
120
+ "variable_name"
121
+ ] = f"{data['nest'].replace('/','_').replace(':','_')}_{str(data['key']).replace(':','_')}"
122
+ data["param_key"] = data["path"].translate(replacements)
123
+
124
+ return data
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "6.2.0"
2
+ __version__ = "6.3.0"
@@ -1 +0,0 @@
1
- stac-fastapi.core==6.2.0
@@ -1,166 +0,0 @@
1
- """patch helpers."""
2
-
3
- import re
4
- from typing import Any, Dict, Optional, Union
5
-
6
- from pydantic import BaseModel, computed_field, model_validator
7
-
8
- regex = re.compile(r"([^.' ]*:[^.'[ ]*)\.?")
9
- replacements = str.maketrans({"/": "", ".": "", ":": "", "[": "", "]": ""})
10
-
11
-
12
- class ESCommandSet:
13
- """Uses dictionary keys to behaviour of ordered set.
14
-
15
- Yields:
16
- str: Elasticsearch commands
17
- """
18
-
19
- dict_: Dict[str, None] = {}
20
-
21
- def __init__(self):
22
- """Initialise ESCommandSet instance."""
23
- self.dict_ = {}
24
-
25
- def add(self, value: str):
26
- """Add command.
27
-
28
- Args:
29
- value (str): value to be added
30
- """
31
- self.dict_[value] = None
32
-
33
- def remove(self, value: str):
34
- """Remove command.
35
-
36
- Args:
37
- value (str): value to be removed
38
- """
39
- del self.dict_[value]
40
-
41
- def __iter__(self):
42
- """Iterate Elasticsearch commands.
43
-
44
- Yields:
45
- str: Elasticsearch command
46
- """
47
- yield from self.dict_.keys()
48
-
49
-
50
- def to_es(string: str):
51
- """Convert patch operation key to Elasticsearch key.
52
-
53
- Args:
54
- string (str): string to be converted
55
-
56
- Returns:
57
- _type_: converted string
58
- """
59
- if matches := regex.findall(string):
60
- for match in set(matches):
61
- string = re.sub(rf"\.?{match}", f"['{match}']", string)
62
-
63
- return string
64
-
65
-
66
- class ElasticPath(BaseModel):
67
- """Converts a JSON path to an Elasticsearch path.
68
-
69
- Args:
70
- path (str): JSON path to be converted.
71
-
72
- """
73
-
74
- path: str
75
- nest: Optional[str] = None
76
- partition: Optional[str] = None
77
- key: Optional[str] = None
78
-
79
- es_path: Optional[str] = None
80
- es_nest: Optional[str] = None
81
- es_key: Optional[str] = None
82
-
83
- index_: Optional[int] = None
84
-
85
- @model_validator(mode="before")
86
- @classmethod
87
- def validate_model(cls, data: Any):
88
- """Set optional fields from JSON path.
89
-
90
- Args:
91
- data (Any): input data
92
- """
93
- data["path"] = data["path"].lstrip("/").replace("/", ".")
94
- data["nest"], data["partition"], data["key"] = data["path"].rpartition(".")
95
-
96
- if data["key"].lstrip("-").isdigit() or data["key"] == "-":
97
- data["index_"] = -1 if data["key"] == "-" else int(data["key"])
98
- data["path"] = f"{data['nest']}[{data['index_']}]"
99
- data["nest"], data["partition"], data["key"] = data["nest"].rpartition(".")
100
-
101
- data["es_path"] = to_es(data["path"])
102
- data["es_nest"] = f".{to_es(data['nest'])}" if data["nest"] else ""
103
- data["es_key"] = to_es(data["key"])
104
-
105
- return data
106
-
107
- @computed_field # type: ignore[misc]
108
- @property
109
- def index(self) -> Union[int, str, None]:
110
- """Compute location of path.
111
-
112
- Returns:
113
- str: path index
114
- """
115
- if self.index_ and self.index_ < 0:
116
-
117
- return f"ctx._source.{self.location}.size() - {-self.index_}"
118
-
119
- return self.index_
120
-
121
- @computed_field # type: ignore[misc]
122
- @property
123
- def location(self) -> str:
124
- """Compute location of path.
125
-
126
- Returns:
127
- str: path location
128
- """
129
- return self.nest + self.partition + self.key
130
-
131
- @computed_field # type: ignore[misc]
132
- @property
133
- def es_location(self) -> str:
134
- """Compute location of path.
135
-
136
- Returns:
137
- str: path location
138
- """
139
- if self.es_key and ":" in self.es_key:
140
- return self.es_nest + self.es_key
141
- return self.es_nest + self.partition + self.es_key
142
-
143
- @computed_field # type: ignore[misc]
144
- @property
145
- def variable_name(self) -> str:
146
- """Variable name for scripting.
147
-
148
- Returns:
149
- str: variable name
150
- """
151
- if self.index is not None:
152
- return f"{self.location.replace('.','_').replace(':','_')}_{self.index}"
153
-
154
- return (
155
- f"{self.nest.replace('.','_').replace(':','_')}_{self.key.replace(':','_')}"
156
- )
157
-
158
- @computed_field # type: ignore[misc]
159
- @property
160
- def param_key(self) -> str:
161
- """Param key for scripting.
162
-
163
- Returns:
164
- str: param key
165
- """
166
- return self.path.translate(replacements)
File without changes