sfeos-tools 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,3 @@
1
+ """SFEOS Tools - Utilities for managing stac-fastapi-elasticsearch-opensearch deployments."""
2
+
3
+ __version__ = "0.1.0"
sfeos_tools/cli.py ADDED
@@ -0,0 +1,336 @@
1
+ """SFEOS CLI Tools - Utilities for managing stac-fastapi-elasticsearch-opensearch deployments.
2
+
3
+ This tool provides various utilities for managing and maintaining SFEOS deployments,
4
+ including database migrations, maintenance tasks, and more.
5
+
6
+ Usage:
7
+ sfeos-tools add-bbox-shape --backend elasticsearch
8
+ sfeos-tools add-bbox-shape --backend opensearch
9
+ """
10
+
11
+ import asyncio
12
+ import logging
13
+ import sys
14
+
15
+ import click
16
+ from stac_fastapi.sfeos_helpers.database import add_bbox_shape_to_collection
17
+ from stac_fastapi.sfeos_helpers.mappings import COLLECTIONS_INDEX
18
+
19
+ from .reindex import run as unified_reindex_run
20
+
21
+ logging.basicConfig(level=logging.INFO)
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ async def process_collection_bbox_shape(client, collection_doc, backend):
26
+ """Process a single collection document to add bbox_shape field.
27
+
28
+ Args:
29
+ client: Elasticsearch/OpenSearch client
30
+ collection_doc: Collection document from database
31
+ backend: Backend type ('elasticsearch' or 'opensearch')
32
+
33
+ Returns:
34
+ bool: True if collection was updated, False if no update was needed
35
+ """
36
+ collection = collection_doc["_source"]
37
+ collection_id = collection.get("id", collection_doc["_id"])
38
+
39
+ # Use the shared function to add bbox_shape
40
+ was_added = add_bbox_shape_to_collection(collection)
41
+
42
+ if not was_added:
43
+ return False
44
+
45
+ # Update the collection in the database
46
+ if backend == "elasticsearch":
47
+ await client.index(
48
+ index=COLLECTIONS_INDEX,
49
+ id=collection_id,
50
+ document=collection,
51
+ refresh=True,
52
+ )
53
+ else: # opensearch
54
+ await client.index(
55
+ index=COLLECTIONS_INDEX,
56
+ id=collection_id,
57
+ body=collection,
58
+ refresh=True,
59
+ )
60
+
61
+ logger.info(f"Collection '{collection_id}': Added bbox_shape field")
62
+ return True
63
+
64
+
65
+ async def run_add_bbox_shape(backend):
66
+ """Add bbox_shape field to all existing collections.
67
+
68
+ Args:
69
+ backend: Backend type ('elasticsearch' or 'opensearch')
70
+ """
71
+ import os
72
+
73
+ logger.info(
74
+ f"Starting migration: Adding bbox_shape to existing collections ({backend})"
75
+ )
76
+
77
+ # Log connection info (showing what will be used by the client)
78
+ es_host = os.getenv("ES_HOST", "localhost")
79
+ es_port = os.getenv(
80
+ "ES_PORT", "9200"
81
+ ) # Both backends default to 9200 in their config
82
+ es_use_ssl = os.getenv("ES_USE_SSL", "true")
83
+ logger.info(f"Connecting to {backend} at {es_host}:{es_port} (SSL: {es_use_ssl})")
84
+
85
+ # Create client based on backend
86
+ if backend == "elasticsearch":
87
+ from stac_fastapi.elasticsearch.config import AsyncElasticsearchSettings
88
+
89
+ settings = AsyncElasticsearchSettings()
90
+ else: # opensearch
91
+ from stac_fastapi.opensearch.config import AsyncOpensearchSettings
92
+
93
+ settings = AsyncOpensearchSettings()
94
+
95
+ client = settings.create_client
96
+
97
+ try:
98
+ # Get all collections
99
+ response = await client.search(
100
+ index=COLLECTIONS_INDEX,
101
+ body={
102
+ "query": {"match_all": {}},
103
+ "size": 10000,
104
+ }, # Adjust size if you have more collections
105
+ )
106
+
107
+ total_collections = response["hits"]["total"]["value"]
108
+ logger.info(f"Found {total_collections} collections to process")
109
+
110
+ updated_count = 0
111
+ skipped_count = 0
112
+
113
+ for hit in response["hits"]["hits"]:
114
+ was_updated = await process_collection_bbox_shape(client, hit, backend)
115
+ if was_updated:
116
+ updated_count += 1
117
+ else:
118
+ skipped_count += 1
119
+
120
+ logger.info(
121
+ f"Migration complete: {updated_count} collections updated, {skipped_count} skipped"
122
+ )
123
+
124
+ except Exception as e:
125
+ logger.error(f"Migration failed with error: {e}")
126
+ raise
127
+ finally:
128
+ await client.close()
129
+
130
+
131
+ @click.group()
132
+ @click.version_option(version="0.1.0", prog_name="sfeos-tools")
133
+ def cli():
134
+ """SFEOS Tools - Utilities for managing stac-fastapi-elasticsearch-opensearch deployments."""
135
+ pass
136
+
137
+
138
+ @cli.command("add-bbox-shape")
139
+ @click.option(
140
+ "--backend",
141
+ type=click.Choice(["elasticsearch", "opensearch"], case_sensitive=False),
142
+ required=True,
143
+ help="Database backend to use",
144
+ )
145
+ @click.option(
146
+ "--host",
147
+ type=str,
148
+ default=None,
149
+ help="Database host (default: localhost or ES_HOST env var)",
150
+ )
151
+ @click.option(
152
+ "--port",
153
+ type=int,
154
+ default=None,
155
+ help="Database port (default: 9200 for ES, 9202 for OS, or ES_PORT env var)",
156
+ )
157
+ @click.option(
158
+ "--use-ssl/--no-ssl",
159
+ default=None,
160
+ help="Use SSL connection (default: true or ES_USE_SSL env var)",
161
+ )
162
+ @click.option(
163
+ "--user",
164
+ type=str,
165
+ default=None,
166
+ help="Database username (default: ES_USER env var)",
167
+ )
168
+ @click.option(
169
+ "--password",
170
+ type=str,
171
+ default=None,
172
+ help="Database password (default: ES_PASS env var)",
173
+ )
174
+ def add_bbox_shape(backend, host, port, use_ssl, user, password):
175
+ """Add bbox_shape field to existing collections for spatial search support.
176
+
177
+ This migration is required for collections created before spatial search
178
+ was added. Collections created or updated after this feature will
179
+ automatically have the bbox_shape field.
180
+
181
+ Examples:
182
+ sfeos_tools.py add-bbox-shape --backend elasticsearch
183
+ sfeos_tools.py add-bbox-shape --backend opensearch --host db.example.com --port 9200
184
+ sfeos_tools.py add-bbox-shape --backend elasticsearch --no-ssl --host localhost
185
+ """
186
+ import os
187
+
188
+ # Set environment variables from CLI options if provided
189
+ if host:
190
+ os.environ["ES_HOST"] = host
191
+ if port:
192
+ os.environ["ES_PORT"] = str(port)
193
+ if use_ssl is not None:
194
+ os.environ["ES_USE_SSL"] = "true" if use_ssl else "false"
195
+ if user:
196
+ os.environ["ES_USER"] = user
197
+ if password:
198
+ os.environ["ES_PASS"] = password
199
+
200
+ try:
201
+ asyncio.run(run_add_bbox_shape(backend.lower()))
202
+ click.echo(click.style("✓ Migration completed successfully", fg="green"))
203
+ except KeyboardInterrupt:
204
+ click.echo(click.style("\n✗ Migration interrupted by user", fg="yellow"))
205
+ sys.exit(1)
206
+ except Exception as e:
207
+ error_msg = str(e)
208
+ click.echo(click.style(f"✗ Migration failed: {error_msg}", fg="red"))
209
+
210
+ # Provide helpful hints for common errors
211
+ if "TLS" in error_msg or "SSL" in error_msg:
212
+ click.echo(
213
+ click.style(
214
+ "\n💡 Hint: If you're connecting to a local Docker Compose instance, "
215
+ "try adding --no-ssl flag",
216
+ fg="yellow",
217
+ )
218
+ )
219
+ elif "Connection refused" in error_msg:
220
+ click.echo(
221
+ click.style(
222
+ "\n💡 Hint: Make sure your database is running and accessible at the specified host:port",
223
+ fg="yellow",
224
+ )
225
+ )
226
+ sys.exit(1)
227
+
228
+
229
+ @cli.command("reindex")
230
+ @click.option(
231
+ "--backend",
232
+ type=click.Choice(["elasticsearch", "opensearch"], case_sensitive=False),
233
+ required=True,
234
+ help="Database backend to use",
235
+ )
236
+ @click.option(
237
+ "--host",
238
+ type=str,
239
+ default=None,
240
+ help="Database host (default: localhost or ES_HOST env var)",
241
+ )
242
+ @click.option(
243
+ "--port",
244
+ type=int,
245
+ default=None,
246
+ help="Database port (default: 9200 for ES, 9202 for OS, or ES_PORT env var)",
247
+ )
248
+ @click.option(
249
+ "--use-ssl/--no-ssl",
250
+ default=None,
251
+ help="Use SSL connection (default: true or ES_USE_SSL env var)",
252
+ )
253
+ @click.option(
254
+ "--user",
255
+ type=str,
256
+ default=None,
257
+ help="Database username (default: ES_USER env var)",
258
+ )
259
+ @click.option(
260
+ "--password",
261
+ type=str,
262
+ default=None,
263
+ help="Database password (default: ES_PASS env var)",
264
+ )
265
+ @click.option(
266
+ "--yes",
267
+ is_flag=True,
268
+ help="Skip confirmation prompt",
269
+ )
270
+ def reindex(backend, host, port, use_ssl, user, password, yes):
271
+ """Reindex all STAC indexes to the next version and update aliases.
272
+
273
+ For Elasticsearch, this runs a migration that:
274
+ - Creates/updates index templates
275
+ - Reindexes collections and item indexes to a new version
276
+ - Applies asset migration script for compatibility
277
+ - Switches aliases to the new indexes
278
+ """
279
+ import os
280
+
281
+ backend = backend.lower()
282
+
283
+ if not yes:
284
+ proceed = click.confirm(
285
+ "This will reindex all collections and item indexes and update aliases. Proceed?",
286
+ default=False,
287
+ )
288
+ if not proceed:
289
+ click.echo(click.style("Aborted", fg="yellow"))
290
+ return
291
+
292
+ # Set environment variables from CLI options if provided
293
+ if host:
294
+ os.environ["ES_HOST"] = host
295
+ if port:
296
+ os.environ["ES_PORT"] = str(port)
297
+ if use_ssl is not None:
298
+ os.environ["ES_USE_SSL"] = "true" if use_ssl else "false"
299
+ if user:
300
+ os.environ["ES_USER"] = user
301
+ if password:
302
+ os.environ["ES_PASS"] = password
303
+
304
+ try:
305
+ asyncio.run(unified_reindex_run(backend))
306
+ click.echo(
307
+ click.style(
308
+ f"✓ Reindex ({backend.title()}) completed successfully", fg="green"
309
+ )
310
+ )
311
+ except KeyboardInterrupt:
312
+ click.echo(click.style("\n✗ Reindex interrupted by user", fg="yellow"))
313
+ sys.exit(1)
314
+ except Exception as e:
315
+ error_msg = str(e)
316
+ click.echo(click.style(f"✗ Reindex failed: {error_msg}", fg="red"))
317
+ # Provide helpful hints for common errors
318
+ if "TLS" in error_msg or "SSL" in error_msg:
319
+ click.echo(
320
+ click.style(
321
+ "\n💡 Hint: If you're connecting to a local Docker Compose instance, try adding --no-ssl flag",
322
+ fg="yellow",
323
+ )
324
+ )
325
+ elif "Connection refused" in error_msg:
326
+ click.echo(
327
+ click.style(
328
+ "\n💡 Hint: Make sure your database is running and accessible at the specified host:port",
329
+ fg="yellow",
330
+ )
331
+ )
332
+ sys.exit(1)
333
+
334
+
335
+ if __name__ == "__main__":
336
+ cli()
sfeos_tools/reindex.py ADDED
@@ -0,0 +1,122 @@
1
+ """Reindex ES/OS database for mapping update."""
2
+
3
+ import asyncio
4
+ import time
5
+ from typing import Any, Dict
6
+
7
+ from stac_fastapi.sfeos_helpers.mappings import COLLECTIONS_INDEX, ITEMS_INDEX_PREFIX
8
+
9
+
10
+ async def _reindex_single_index(
11
+ client, index: str, new_index: str, aliases: Dict[str, Any]
12
+ ):
13
+ """Reindex a single index to a new version and switch aliases."""
14
+ print(f"reindexing {index} to {new_index}")
15
+
16
+ await client.options(ignore_status=400).indices.create(index=new_index)
17
+
18
+ # Asset migration script for data compatibility
19
+ script = {
20
+ "source": (
21
+ "if (ctx._source.containsKey('assets')){List l = new ArrayList();"
22
+ "for (key in ctx._source.assets.keySet()) {def item = ctx._source.assets[key];"
23
+ " item['es_key'] = key; l.add(item)}ctx._source.assets=l} "
24
+ "if (ctx._source.containsKey('item_assets')){ List a = new ArrayList();"
25
+ " for (key in ctx._source.item_assets.keySet()) {def item = ctx._source.item_assets[key];"
26
+ " item['es_key'] = key; a.add(item)}ctx._source.item_assets=a}"
27
+ ),
28
+ "lang": "painless",
29
+ }
30
+
31
+ reindex_resp = await client.reindex(
32
+ dest={"index": new_index},
33
+ source={"index": [index]},
34
+ wait_for_completion=False,
35
+ script=script,
36
+ )
37
+
38
+ task_id = reindex_resp["task"]
39
+
40
+ reindex_complete = False
41
+ while not reindex_complete:
42
+ task_resp = await client.tasks.get(task_id=task_id)
43
+
44
+ if "completed" in task_resp and task_resp["completed"]:
45
+ reindex_complete = True
46
+ elif "error" in task_resp:
47
+ reindex_complete = True
48
+ print(f"Reindex failed for {index} with error: {task_resp['error']}")
49
+ else:
50
+ time.sleep(60)
51
+
52
+ actions = []
53
+ for alias in aliases["aliases"]:
54
+ actions.extend(
55
+ [
56
+ {"add": {"index": new_index, "alias": alias}},
57
+ {"remove": {"index": index, "alias": alias}},
58
+ ]
59
+ )
60
+
61
+ if actions:
62
+ await client.indices.update_aliases(actions=actions)
63
+
64
+
65
+ async def run(backend: str = "elasticsearch"):
66
+ """Reindex all STAC indexes for mapping update for the given backend.
67
+
68
+ backend: 'elasticsearch' or 'opensearch'
69
+ """
70
+ backend = backend.lower()
71
+
72
+ # Lazy imports so the package dependencies remain optional per backend
73
+ if backend == "elasticsearch":
74
+ from stac_fastapi.elasticsearch.config import AsyncElasticsearchSettings
75
+ from stac_fastapi.elasticsearch.database_logic import create_index_templates
76
+
77
+ settings = AsyncElasticsearchSettings()
78
+ elif backend == "opensearch":
79
+ from stac_fastapi.opensearch.config import AsyncOpensearchSettings
80
+ from stac_fastapi.opensearch.database_logic import create_index_templates
81
+
82
+ settings = AsyncOpensearchSettings()
83
+ else:
84
+ raise ValueError(f"Unsupported backend: {backend}")
85
+
86
+ client = settings.create_client
87
+
88
+ try:
89
+ # Ensure latest templates are applied
90
+ await create_index_templates()
91
+
92
+ # Collections index
93
+ collection_response = await client.indices.get_alias(name=COLLECTIONS_INDEX)
94
+ collections = await client.search(index=COLLECTIONS_INDEX)
95
+
96
+ collection_index, collection_aliases = next(iter(collection_response.items()))
97
+ collection_index_name, version = collection_index.rsplit("-", 1)
98
+ new_collection_index = (
99
+ f"{collection_index_name}-{str(int(version) + 1).zfill(6)}"
100
+ )
101
+
102
+ await _reindex_single_index(
103
+ client, collection_index, new_collection_index, collection_aliases
104
+ )
105
+
106
+ # Items per collection
107
+ for collection in collections["hits"]["hits"]:
108
+ item_indexes = await client.indices.get_alias(
109
+ name=f"{ITEMS_INDEX_PREFIX}{collection['_id']}*"
110
+ )
111
+
112
+ for item_index, aliases in item_indexes.items():
113
+ item_index_name, version = item_index.rsplit("-", 1)
114
+ new_item_index = f"{item_index_name}-{str(int(version) + 1).zfill(6)}"
115
+
116
+ await _reindex_single_index(client, item_index, new_item_index, aliases)
117
+ finally:
118
+ await client.close()
119
+
120
+
121
+ if __name__ == "__main__":
122
+ asyncio.run(run("elasticsearch"))
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: sfeos-tools
3
+ Version: 0.1.0
4
+ Summary: CLI tools for managing stac-fastapi-elasticsearch-opensearch deployments
5
+ Author: CloudFerro S.A.
6
+ Author-email: Jonathan Healy <jon@healy-hypersaptial.dev>
7
+ Maintainer-email: Jonathan Healy <jon@healy-hypersaptial.dev>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2025 Healy Hyperspatial, Jonathan Healy and CloudFerro S.A.
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+
30
+ Project-URL: Homepage, https://github.com/Healy-Hyperspatial/sfeos-tools
31
+ Project-URL: Documentation, https://github.com/Healy-Hyperspatial/sfeos-tools#readme
32
+ Project-URL: Issues, https://github.com/Healy-Hyperspatial/sfeos-tools/issues
33
+ Project-URL: Source, https://github.com/Healy-Hyperspatial/sfeos-tools
34
+ Classifier: Development Status :: 4 - Beta
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: Intended Audience :: Science/Research
37
+ Classifier: License :: OSI Approved :: MIT License
38
+ Classifier: Programming Language :: Python :: 3.8
39
+ Classifier: Programming Language :: Python :: 3.9
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Topic :: Scientific/Engineering
44
+ Classifier: Topic :: Scientific/Engineering :: GIS
45
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
46
+ Requires-Python: >=3.8
47
+ Description-Content-Type: text/markdown
48
+ License-File: LICENSE
49
+ Requires-Dist: click>=8.0.0
50
+ Provides-Extra: elasticsearch
51
+ Requires-Dist: stac-fastapi-elasticsearch; extra == "elasticsearch"
52
+ Provides-Extra: opensearch
53
+ Requires-Dist: stac-fastapi-opensearch; extra == "opensearch"
54
+ Provides-Extra: dev
55
+ Requires-Dist: stac-fastapi-elasticsearch; extra == "dev"
56
+ Requires-Dist: stac-fastapi-opensearch; extra == "dev"
57
+ Requires-Dist: pytest>=6.0; extra == "dev"
58
+ Requires-Dist: pytest-cov>=2.0; extra == "dev"
59
+ Requires-Dist: black>=21.0; extra == "dev"
60
+ Requires-Dist: isort>=5.0; extra == "dev"
61
+ Requires-Dist: mypy>=0.900; extra == "dev"
62
+ Requires-Dist: flake8>=4.0.0; extra == "dev"
63
+ Dynamic: license-file
64
+
65
+ # SFEOS Tools
66
+
67
+ CLI tools for managing [stac-fastapi-elasticsearch-opensearch](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch) deployments.
68
+
69
+ ## Table of Contents
70
+
71
+ - [Installation](#installation)
72
+ - [For Elasticsearch](#for-elasticsearch)
73
+ - [For OpenSearch](#for-opensearch)
74
+ - [For Development](#for-development-both-backends)
75
+ - [Usage](#usage)
76
+ - [Commands](#commands)
77
+ - [add-bbox-shape](#add-bbox-shape)
78
+ - [reindex](#reindex)
79
+ - [Development](#development)
80
+ - [License](#license)
81
+
82
+ ## Installation
83
+
84
+ ### For Elasticsearch
85
+
86
+ ```bash
87
+ pip install sfeos-tools[elasticsearch]
88
+ ```
89
+
90
+ Or for local development:
91
+ ```bash
92
+ pip install -e sfeos_tools[elasticsearch]
93
+ ```
94
+
95
+ ### For OpenSearch
96
+
97
+ ```bash
98
+ pip install sfeos-tools[opensearch]
99
+ ```
100
+
101
+ Or for local development:
102
+ ```bash
103
+ pip install -e sfeos_tools[opensearch]
104
+ ```
105
+
106
+ ### For Development (both backends)
107
+
108
+ ```bash
109
+ pip install sfeos-tools[dev]
110
+ ```
111
+
112
+ Or for local development:
113
+ ```bash
114
+ pip install -e sfeos_tools[dev]
115
+ ```
116
+
117
+ ## Usage
118
+
119
+ After installation, the `sfeos-tools` command will be available:
120
+
121
+ ```bash
122
+ # View available commands
123
+ sfeos-tools --help
124
+
125
+ # View version
126
+ sfeos-tools --version
127
+ ```
128
+
129
+ ## Commands
130
+
131
+ ### add-bbox-shape
132
+
133
+ Adds a `bbox_shape` field to existing collections for spatial search support. This migration is required for collections created before spatial search was added. Collections created or updated after this feature will automatically have the `bbox_shape` field.
134
+
135
+ ```bash
136
+ sfeos-tools add-bbox-shape --backend [elasticsearch|opensearch] [options]
137
+ ```
138
+
139
+ Options:
140
+ - `--backend`: Database backend to use (required, choices: elasticsearch, opensearch)
141
+ - `--host`: Database host (default: localhost or ES_HOST env var)
142
+ - `--port`: Database port (default: 9200 for ES, 9202 for OS, or ES_PORT env var)
143
+ - `--use-ssl/--no-ssl`: Use SSL connection (default: true or ES_USE_SSL env var)
144
+ - `--user`: Database username (default: ES_USER env var)
145
+ - `--password`: Database password (default: ES_PASS env var)
146
+
147
+ ### reindex
148
+
149
+ Reindexes all STAC indexes to the next version and updates aliases. This command performs the following actions:
150
+ - Creates/updates index templates
151
+ - Reindexes collections and item indexes to a new version
152
+ - Applies asset migration script for compatibility
153
+ - Switches aliases to the new indexes
154
+
155
+ ```bash
156
+ sfeos-tools reindex --backend [elasticsearch|opensearch] [options]
157
+ ```
158
+
159
+ Options:
160
+ - `--backend`: Database backend to use (required, choices: elasticsearch, opensearch)
161
+ - `--host`: Database host (default: localhost or ES_HOST env var)
162
+ - `--port`: Database port (default: 9200 for ES, 9202 for OS, or ES_PORT env var)
163
+ - `--use-ssl/--no-ssl`: Use SSL connection (default: true or ES_USE_SSL env var)
164
+ - `--user`: Database username (default: ES_USER env var)
165
+ - `--password`: Database password (default: ES_PASS env var)
166
+ - `--yes`: Skip confirmation prompt
167
+
168
+ Example:
169
+ ```bash
170
+ # Reindex Elasticsearch with custom host and no SSL
171
+ sfeos-tools reindex --backend elasticsearch --host localhost --port 9200 --no-ssl
172
+
173
+ # Reindex OpenSearch with default settings
174
+ sfeos-tools reindex --backend opensearch
175
+ ```
176
+
177
+ # Get help for a specific command
178
+ sfeos-tools add-bbox-shape --help
179
+ ```
180
+
181
+ ## Commands
182
+
183
+ ### add-bbox-shape
184
+
185
+ Add `bbox_shape` field to existing collections for spatial search support.
186
+
187
+ **Basic usage:**
188
+
189
+ ```bash
190
+ # Elasticsearch
191
+ sfeos-tools add-bbox-shape --backend elasticsearch
192
+
193
+ # OpenSearch
194
+ sfeos-tools add-bbox-shape --backend opensearch
195
+ ```
196
+
197
+ **Connection options:**
198
+
199
+ ```bash
200
+ # Local Docker Compose (no SSL)
201
+ sfeos-tools add-bbox-shape --backend elasticsearch --no-ssl
202
+
203
+ # Remote server with SSL
204
+ sfeos-tools add-bbox-shape \
205
+ --backend elasticsearch \
206
+ --host db.example.com \
207
+ --port 9200 \
208
+ --user admin \
209
+ --password secret
210
+
211
+ # Using environment variables
212
+ ES_HOST=my-cluster.cloud.com ES_PORT=9243 ES_USER=elastic ES_PASS=changeme \
213
+ sfeos-tools add-bbox-shape --backend elasticsearch
214
+ ```
215
+
216
+ **Available options:**
217
+
218
+ - `--backend`: Database backend (elasticsearch or opensearch) - **required**
219
+ - `--host`: Database host (default: localhost or ES_HOST env var)
220
+ - `--port`: Database port (default: 9200 or ES_PORT env var)
221
+ - `--use-ssl / --no-ssl`: Use SSL connection (default: true or ES_USE_SSL env var)
222
+ - `--user`: Database username (default: ES_USER env var)
223
+ - `--password`: Database password (default: ES_PASS env var)
224
+
225
+ ## Development
226
+
227
+ To develop sfeos-tools locally:
228
+
229
+ ```bash
230
+ # Install in editable mode with dev dependencies
231
+ pip install -e ./sfeos_tools[dev]
232
+
233
+ # Run the CLI
234
+ sfeos-tools --help
235
+
236
+ # Run tests
237
+ pytest
238
+
239
+ # Format code
240
+ pre-commit install
241
+ pre-commit run --all-files
242
+ ```
243
+
@@ -0,0 +1,9 @@
1
+ sfeos_tools/__init__.py,sha256=E6dzjiV5qnPvtjRAEfXy3lS6yqgS6olUV9gW3rnKo2Y,117
2
+ sfeos_tools/cli.py,sha256=eUauJIRjuQZfrZPTfXAMDm_cn4oFNNi1eJWTkcZh-0U,10209
3
+ sfeos_tools/reindex.py,sha256=JIglWrRxvmrSwsTSqKrhqcTJgNKP_5blcNxujFesbgw,4277
4
+ sfeos_tools-0.1.0.dist-info/licenses/LICENSE,sha256=euoUdQTBFz_vxdUnYkUnTUeAtrzfmJnBmGrO3NceeO8,1111
5
+ sfeos_tools-0.1.0.dist-info/METADATA,sha256=1aHsyPTxW28AjEyOSAt-YkeBhWWsbQoKxwh27P3TIjU,7772
6
+ sfeos_tools-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ sfeos_tools-0.1.0.dist-info/entry_points.txt,sha256=fgwnuQndU6x7RNOw4twi0ZkqDFgb5HOijB-wRuo1cp8,52
8
+ sfeos_tools-0.1.0.dist-info/top_level.txt,sha256=wBLaEvh7OmhjMhGluVm2vwE0ufSuc90eHrlTph-51kM,12
9
+ sfeos_tools-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sfeos-tools = sfeos_tools.cli:cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Healy Hyperspatial, Jonathan Healy and CloudFerro S.A.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ sfeos_tools