sfeos-tools 0.1.1__py3-none-any.whl → 0.2.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.
sfeos_tools/cli.py CHANGED
@@ -14,7 +14,15 @@ import sys
14
14
 
15
15
  import click
16
16
 
17
+ try:
18
+ from importlib.metadata import version as _get_version
19
+ except ImportError:
20
+ from importlib_metadata import version as _get_version # type: ignore[no-redef]
21
+
22
+ __version__ = _get_version("sfeos-tools")
23
+
17
24
  from .bbox_shape import run_add_bbox_shape
25
+ from .data_loader import load_items
18
26
  from .reindex import run as unified_reindex_run
19
27
 
20
28
  logging.basicConfig(level=logging.INFO)
@@ -22,7 +30,7 @@ logger = logging.getLogger(__name__)
22
30
 
23
31
 
24
32
  @click.group()
25
- @click.version_option(version="0.1.0", prog_name="sfeos-tools")
33
+ @click.version_option(version=__version__, prog_name="sfeos-tools")
26
34
  def cli():
27
35
  """SFEOS Tools - Utilities for managing stac-fastapi-elasticsearch-opensearch deployments."""
28
36
  pass
@@ -225,5 +233,47 @@ def reindex(backend, host, port, use_ssl, user, password, yes):
225
233
  sys.exit(1)
226
234
 
227
235
 
236
+ @cli.command("load-data")
237
+ @click.option("--base-url", required=True, help="Base URL of the STAC API")
238
+ @click.option(
239
+ "--collection-id",
240
+ default="test-collection",
241
+ help="ID of the collection to which items are added",
242
+ )
243
+ @click.option("--use-bulk", is_flag=True, help="Use bulk insert method for items")
244
+ @click.option(
245
+ "--data-dir",
246
+ type=click.Path(exists=True),
247
+ default="sample_data/",
248
+ help="Directory containing collection.json and feature collection file",
249
+ )
250
+ def load_data(base_url: str, collection_id: str, use_bulk: bool, data_dir: str) -> None:
251
+ """Load STAC items into the database via STAC API.
252
+
253
+ This command loads a STAC collection and its items from local JSON files
254
+ into a STAC API instance. It expects a directory containing:
255
+ - collection.json: The STAC collection definition
256
+ - One or more feature collection JSON files with STAC items
257
+
258
+ Examples:
259
+ sfeos-tools load-data --base-url http://localhost:8080
260
+ sfeos-tools load-data --base-url http://localhost:8080 --collection-id my-collection --use-bulk
261
+ sfeos-tools load-data --base-url http://localhost:8080 --data-dir /path/to/data
262
+ """
263
+ from httpx import Client
264
+
265
+ try:
266
+ with Client(base_url=base_url) as client:
267
+ load_items(client, collection_id, use_bulk, data_dir)
268
+ click.echo(click.style("✓ Data loading completed successfully", fg="green"))
269
+ except KeyboardInterrupt:
270
+ click.echo(click.style("\n✗ Data loading interrupted by user", fg="yellow"))
271
+ sys.exit(1)
272
+ except Exception as e:
273
+ error_msg = str(e)
274
+ click.echo(click.style(f"✗ Data loading failed: {error_msg}", fg="red"))
275
+ sys.exit(1)
276
+
277
+
228
278
  if __name__ == "__main__":
229
279
  cli()
@@ -0,0 +1,102 @@
1
+ """Data Loader CLI STAC_API Ingestion Tool."""
2
+
3
+ import os
4
+ from typing import Any
5
+
6
+ import click
7
+ import orjson
8
+ from httpx import Client
9
+
10
+
11
+ def load_data(filepath: str) -> dict[str, Any]:
12
+ """Load json data from a file within the specified data directory."""
13
+ try:
14
+ with open(filepath, "rb") as file:
15
+ return orjson.loads(file.read())
16
+ except FileNotFoundError as e:
17
+ click.secho(f"File not found: {filepath}", fg="red", err=True)
18
+ raise click.Abort() from e
19
+
20
+
21
+ def load_collection(client: Client, collection_id: str, data_dir: str) -> None:
22
+ """Load a STAC collection into the database."""
23
+ collection = load_data(os.path.join(data_dir, "collection.json"))
24
+ collection["id"] = collection_id
25
+ resp = client.post("/collections", json=collection)
26
+ if resp.status_code == 200 or resp.status_code == 201:
27
+ click.echo(f"Status code: {resp.status_code}")
28
+ click.echo(f"Added collection: {collection['id']}")
29
+ elif resp.status_code == 409:
30
+ click.echo(f"Status code: {resp.status_code}")
31
+ click.echo(f"Collection: {collection['id']} already exists")
32
+ else:
33
+ click.echo(f"Status code: {resp.status_code}")
34
+ click.echo(f"Error writing {collection['id']} collection. Message: {resp.text}")
35
+
36
+
37
+ def load_items(
38
+ client: Client, collection_id: str, use_bulk: bool, data_dir: str
39
+ ) -> None:
40
+ """Load STAC items into the database based on the method selected."""
41
+ with os.scandir(data_dir) as entries:
42
+ # Attempt to dynamically find a suitable feature collection file
43
+ # Use the first found feature collection file
44
+ feature_file = next(
45
+ (
46
+ entry.path
47
+ for entry in entries
48
+ if entry.is_file()
49
+ and entry.name.endswith(".json")
50
+ and entry.name != "collection.json"
51
+ ),
52
+ None,
53
+ )
54
+
55
+ if feature_file is None:
56
+ click.secho(
57
+ "No feature collection files found in the specified directory.",
58
+ fg="red",
59
+ err=True,
60
+ )
61
+ raise click.Abort()
62
+
63
+ feature_collection = load_data(feature_file)
64
+
65
+ load_collection(client, collection_id, data_dir)
66
+ if use_bulk:
67
+ load_items_bulk_insert(client, collection_id, feature_collection)
68
+ else:
69
+ load_items_one_by_one(client, collection_id, feature_collection)
70
+
71
+
72
+ def load_items_one_by_one(
73
+ client: Client, collection_id: str, feature_collection: dict[str, Any]
74
+ ) -> None:
75
+ """Load STAC items into the database one by one."""
76
+ for feature in feature_collection["features"]:
77
+ feature["collection"] = collection_id
78
+ resp = client.post(f"/collections/{collection_id}/items", json=feature)
79
+ if resp.status_code == 200:
80
+ click.echo(f"Status code: {resp.status_code}")
81
+ click.echo(f"Added item: {feature['id']}")
82
+ elif resp.status_code == 409:
83
+ click.echo(f"Status code: {resp.status_code}")
84
+ click.echo(f"Item: {feature['id']} already exists")
85
+
86
+
87
+ def load_items_bulk_insert(
88
+ client: Client, collection_id: str, feature_collection: dict[str, Any]
89
+ ) -> None:
90
+ """Load STAC items into the database via bulk insert."""
91
+ for feature in feature_collection["features"]:
92
+ feature["collection"] = collection_id
93
+ resp = client.post(f"/collections/{collection_id}/items", json=feature_collection)
94
+ if resp.status_code == 200:
95
+ click.echo(f"Status code: {resp.status_code}")
96
+ click.echo("Bulk inserted items successfully.")
97
+ elif resp.status_code == 204:
98
+ click.echo(f"Status code: {resp.status_code}")
99
+ click.echo("Bulk update successful, no content returned.")
100
+ elif resp.status_code == 409:
101
+ click.echo(f"Status code: {resp.status_code}")
102
+ click.echo("Conflict detected, some items might already exist.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sfeos-tools
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: CLI tools for managing stac-fastapi-elasticsearch-opensearch deployments
5
5
  Author: CloudFerro S.A.
6
6
  Author-email: Jonathan Healy <jon@healy-hypersaptial.dev>
@@ -48,6 +48,8 @@ Requires-Python: >=3.8
48
48
  Description-Content-Type: text/markdown
49
49
  License-File: LICENSE
50
50
  Requires-Dist: click>=8.0.0
51
+ Requires-Dist: httpx>=0.24.0
52
+ Requires-Dist: orjson>=3.0.0
51
53
  Provides-Extra: elasticsearch
52
54
  Requires-Dist: stac-fastapi-elasticsearch>=6.6.0; extra == "elasticsearch"
53
55
  Provides-Extra: opensearch
@@ -94,6 +96,7 @@ CLI tools for managing [stac-fastapi-elasticsearch-opensearch](https://github.co
94
96
  - [Commands](#commands)
95
97
  - [add-bbox-shape](#add-bbox-shape)
96
98
  - [reindex](#reindex)
99
+ - [load-data](#load-data)
97
100
  - [Development](#development)
98
101
  - [License](#license)
99
102
 
@@ -183,63 +186,56 @@ Options:
183
186
  - `--password`: Database password (default: ES_PASS env var)
184
187
  - `--yes`: Skip confirmation prompt
185
188
 
186
- Example:
189
+ Examples:
187
190
  ```bash
188
191
  # Reindex Elasticsearch with custom host and no SSL
189
- sfeos-tools reindex --backend elasticsearch --host localhost --port 9200 --no-ssl
192
+ sfeos-tools reindex --backend elasticsearch --host localhost --port 9200 --no-ssl --yes
190
193
 
191
194
  # Reindex OpenSearch with default settings
192
- sfeos-tools reindex --backend opensearch
195
+ sfeos-tools reindex --backend opensearch --yes
193
196
  ```
194
197
 
195
- # Get help for a specific command
196
- sfeos-tools add-bbox-shape --help
197
- ```
198
-
199
- ## Commands
200
-
201
- ### add-bbox-shape
202
-
203
- Add `bbox_shape` field to existing collections for spatial search support.
198
+ ### load-data
204
199
 
205
- **Basic usage:**
200
+ Load STAC collections and items from local JSON files into a STAC API instance. This command is useful for:
201
+ - Populating a new STAC API deployment with test data
202
+ - Migrating data between STAC API instances
203
+ - Bulk loading STAC collections and items
206
204
 
207
205
  ```bash
208
- # Elasticsearch
209
- sfeos-tools add-bbox-shape --backend elasticsearch
210
-
211
- # OpenSearch
212
- sfeos-tools add-bbox-shape --backend opensearch
206
+ sfeos-tools load-data --base-url <stac-api-url> [options]
213
207
  ```
214
208
 
215
- **Connection options:**
209
+ Options:
210
+ - `--base-url`: Base URL of the STAC API (required)
211
+ - `--collection-id`: ID of the collection to create/update (default: test-collection)
212
+ - `--data-dir`: Directory containing collection.json and feature collection files (default: sample_data/)
213
+ - `--use-bulk`: Use bulk insert method for items (faster for large datasets)
214
+
215
+ **Data Directory Structure:**
216
+
217
+ Your data directory should contain:
218
+ - `collection.json`: STAC collection definition
219
+ - One or more `.json` files: Feature collections with STAC items
216
220
 
221
+ Examples:
217
222
  ```bash
218
- # Local Docker Compose (no SSL)
219
- sfeos-tools add-bbox-shape --backend elasticsearch --no-ssl
220
-
221
- # Remote server with SSL
222
- sfeos-tools add-bbox-shape \
223
- --backend elasticsearch \
224
- --host db.example.com \
225
- --port 9200 \
226
- --user admin \
227
- --password secret
228
-
229
- # Using environment variables
230
- ES_HOST=my-cluster.cloud.com ES_PORT=9243 ES_USER=elastic ES_PASS=changeme \
231
- sfeos-tools add-bbox-shape --backend elasticsearch
223
+ # Load data from default directory
224
+ sfeos-tools load-data --base-url http://localhost:8080
225
+
226
+ # Load with custom collection ID and bulk insert
227
+ sfeos-tools load-data \
228
+ --base-url http://localhost:8080 \
229
+ --collection-id my-collection \
230
+ --use-bulk
231
+
232
+ # Load from custom directory
233
+ sfeos-tools load-data \
234
+ --base-url http://localhost:8080 \
235
+ --data-dir /path/to/stac/data \
236
+ --collection-id production-data
232
237
  ```
233
238
 
234
- **Available options:**
235
-
236
- - `--backend`: Database backend (elasticsearch or opensearch) - **required**
237
- - `--host`: Database host (default: localhost or ES_HOST env var)
238
- - `--port`: Database port (default: 9200 or ES_PORT env var)
239
- - `--use-ssl / --no-ssl`: Use SSL connection (default: true or ES_USE_SSL env var)
240
- - `--user`: Database username (default: ES_USER env var)
241
- - `--password`: Database password (default: ES_PASS env var)
242
-
243
239
  ## Development
244
240
 
245
241
  To develop sfeos-tools locally:
@@ -259,3 +255,6 @@ pre-commit install
259
255
  pre-commit run --all-files
260
256
  ```
261
257
 
258
+ ## License
259
+
260
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,11 @@
1
+ sfeos_tools/__init__.py,sha256=E6dzjiV5qnPvtjRAEfXy3lS6yqgS6olUV9gW3rnKo2Y,117
2
+ sfeos_tools/bbox_shape.py,sha256=lL7vq-lD36XKcZ4ScZF6tDs-KRW9DHCiDquXaE9gL9A,3491
3
+ sfeos_tools/cli.py,sha256=eTm7cfyuYU8x2KQ3ORBMmbw2AX1Ys3ct2tkjbzOBwQc,8838
4
+ sfeos_tools/data_loader.py,sha256=dh_JL7qxIYajVS4Dhz2hJRSJ-L1k-c_6K4V8VXgumcs,3876
5
+ sfeos_tools/reindex.py,sha256=JIglWrRxvmrSwsTSqKrhqcTJgNKP_5blcNxujFesbgw,4277
6
+ sfeos_tools-0.2.0.dist-info/licenses/LICENSE,sha256=euoUdQTBFz_vxdUnYkUnTUeAtrzfmJnBmGrO3NceeO8,1111
7
+ sfeos_tools-0.2.0.dist-info/METADATA,sha256=4I8ebe3FciZ6WsYMC-Q3ZSg_fFgivMJ6G_WmM-7mCV8,9394
8
+ sfeos_tools-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ sfeos_tools-0.2.0.dist-info/entry_points.txt,sha256=fgwnuQndU6x7RNOw4twi0ZkqDFgb5HOijB-wRuo1cp8,52
10
+ sfeos_tools-0.2.0.dist-info/top_level.txt,sha256=wBLaEvh7OmhjMhGluVm2vwE0ufSuc90eHrlTph-51kM,12
11
+ sfeos_tools-0.2.0.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- sfeos_tools/__init__.py,sha256=E6dzjiV5qnPvtjRAEfXy3lS6yqgS6olUV9gW3rnKo2Y,117
2
- sfeos_tools/bbox_shape.py,sha256=lL7vq-lD36XKcZ4ScZF6tDs-KRW9DHCiDquXaE9gL9A,3491
3
- sfeos_tools/cli.py,sha256=s6fK1uyqTXY1avaFAodBDnAOU8NUKqYQvqavQX83Kgk,6876
4
- sfeos_tools/reindex.py,sha256=JIglWrRxvmrSwsTSqKrhqcTJgNKP_5blcNxujFesbgw,4277
5
- sfeos_tools-0.1.1.dist-info/licenses/LICENSE,sha256=euoUdQTBFz_vxdUnYkUnTUeAtrzfmJnBmGrO3NceeO8,1111
6
- sfeos_tools-0.1.1.dist-info/METADATA,sha256=NARvC1EiJj68s2K45U6KDKzRa-04tfpPzrh_mmQR4Qs,9137
7
- sfeos_tools-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- sfeos_tools-0.1.1.dist-info/entry_points.txt,sha256=fgwnuQndU6x7RNOw4twi0ZkqDFgb5HOijB-wRuo1cp8,52
9
- sfeos_tools-0.1.1.dist-info/top_level.txt,sha256=wBLaEvh7OmhjMhGluVm2vwE0ufSuc90eHrlTph-51kM,12
10
- sfeos_tools-0.1.1.dist-info/RECORD,,