sfeos-tools 0.1.0__tar.gz → 0.1.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sfeos-tools
3
- Version: 0.1.0
3
+ Version: 0.1.1
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>
@@ -35,11 +35,12 @@ Classifier: Development Status :: 4 - Beta
35
35
  Classifier: Intended Audience :: Developers
36
36
  Classifier: Intended Audience :: Science/Research
37
37
  Classifier: License :: OSI Approved :: MIT License
38
- Classifier: Programming Language :: Python :: 3.8
39
38
  Classifier: Programming Language :: Python :: 3.9
40
39
  Classifier: Programming Language :: Python :: 3.10
41
40
  Classifier: Programming Language :: Python :: 3.11
42
41
  Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Programming Language :: Python :: 3.13
43
+ Classifier: Programming Language :: Python :: 3.14
43
44
  Classifier: Topic :: Scientific/Engineering
44
45
  Classifier: Topic :: Scientific/Engineering :: GIS
45
46
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
@@ -48,13 +49,14 @@ Description-Content-Type: text/markdown
48
49
  License-File: LICENSE
49
50
  Requires-Dist: click>=8.0.0
50
51
  Provides-Extra: elasticsearch
51
- Requires-Dist: stac-fastapi-elasticsearch; extra == "elasticsearch"
52
+ Requires-Dist: stac-fastapi-elasticsearch>=6.6.0; extra == "elasticsearch"
52
53
  Provides-Extra: opensearch
53
- Requires-Dist: stac-fastapi-opensearch; extra == "opensearch"
54
+ Requires-Dist: stac-fastapi-opensearch>=6.6.0; extra == "opensearch"
54
55
  Provides-Extra: dev
55
- Requires-Dist: stac-fastapi-elasticsearch; extra == "dev"
56
- Requires-Dist: stac-fastapi-opensearch; extra == "dev"
56
+ Requires-Dist: stac-fastapi-elasticsearch>=6.6.0; extra == "dev"
57
+ Requires-Dist: stac-fastapi-opensearch>=6.6.0; extra == "dev"
57
58
  Requires-Dist: pytest>=6.0; extra == "dev"
59
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
58
60
  Requires-Dist: pytest-cov>=2.0; extra == "dev"
59
61
  Requires-Dist: black>=21.0; extra == "dev"
60
62
  Requires-Dist: isort>=5.0; extra == "dev"
@@ -66,6 +68,22 @@ Dynamic: license-file
66
68
 
67
69
  CLI tools for managing [stac-fastapi-elasticsearch-opensearch](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch) deployments.
68
70
 
71
+ <!-- markdownlint-disable MD033 MD041 -->
72
+
73
+
74
+ <p align="left">
75
+ <img src="https://raw.githubusercontent.com/stac-utils/stac-fastapi-elasticsearch-opensearch/refs/heads/main/assets/sfeos.png" width=1000>
76
+ </p>
77
+
78
+ <!-- **Jump to:** [Project Introduction](#project-introduction---what-is-sfeos) | [Quick Start](#quick-start) | [Table of Contents](#table-of-contents) -->
79
+
80
+ [![Downloads](https://static.pepy.tech/badge/sfeos-tools?color=blue)](https://pepy.tech/project/sfeos-tools)
81
+ [![GitHub contributors](https://img.shields.io/github/contributors/healy-hyperspatial/sfeos-tools?color=blue)](https://github.com/healy-hyperspatial/sfeos-tools/graphs/contributors)
82
+ [![GitHub stars](https://img.shields.io/github/stars/healy-hyperspatial/sfeos-tools.svg?color=blue)](https://github.com/healy-hyperspatial/sfeos-tools/stargazers)
83
+ [![GitHub forks](https://img.shields.io/github/forks/healy-hyperspatial/sfeos-tools.svg?color=blue)](https://github.com/healy-hyperspatial/sfeos-tools/network/members)
84
+ [![PyPI version](https://img.shields.io/pypi/v/sfeos-tools.svg?color=blue)](https://pypi.org/project/sfeos-tools/)
85
+ [![STAC](https://img.shields.io/badge/STAC-1.1.0-blue.svg)](https://github.com/radiantearth/stac-spec/tree/v1.1.0)
86
+
69
87
  ## Table of Contents
70
88
 
71
89
  - [Installation](#installation)
@@ -2,6 +2,22 @@
2
2
 
3
3
  CLI tools for managing [stac-fastapi-elasticsearch-opensearch](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch) deployments.
4
4
 
5
+ <!-- markdownlint-disable MD033 MD041 -->
6
+
7
+
8
+ <p align="left">
9
+ <img src="https://raw.githubusercontent.com/stac-utils/stac-fastapi-elasticsearch-opensearch/refs/heads/main/assets/sfeos.png" width=1000>
10
+ </p>
11
+
12
+ <!-- **Jump to:** [Project Introduction](#project-introduction---what-is-sfeos) | [Quick Start](#quick-start) | [Table of Contents](#table-of-contents) -->
13
+
14
+ [![Downloads](https://static.pepy.tech/badge/sfeos-tools?color=blue)](https://pepy.tech/project/sfeos-tools)
15
+ [![GitHub contributors](https://img.shields.io/github/contributors/healy-hyperspatial/sfeos-tools?color=blue)](https://github.com/healy-hyperspatial/sfeos-tools/graphs/contributors)
16
+ [![GitHub stars](https://img.shields.io/github/stars/healy-hyperspatial/sfeos-tools.svg?color=blue)](https://github.com/healy-hyperspatial/sfeos-tools/stargazers)
17
+ [![GitHub forks](https://img.shields.io/github/forks/healy-hyperspatial/sfeos-tools.svg?color=blue)](https://github.com/healy-hyperspatial/sfeos-tools/network/members)
18
+ [![PyPI version](https://img.shields.io/pypi/v/sfeos-tools.svg?color=blue)](https://pypi.org/project/sfeos-tools/)
19
+ [![STAC](https://img.shields.io/badge/STAC-1.1.0-blue.svg)](https://github.com/radiantearth/stac-spec/tree/v1.1.0)
20
+
5
21
  ## Table of Contents
6
22
 
7
23
  - [Installation](#installation)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sfeos-tools"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "CLI tools for managing stac-fastapi-elasticsearch-opensearch deployments"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -21,11 +21,12 @@ classifiers = [
21
21
  "Intended Audience :: Developers",
22
22
  "Intended Audience :: Science/Research",
23
23
  "License :: OSI Approved :: MIT License",
24
- "Programming Language :: Python :: 3.8",
25
24
  "Programming Language :: Python :: 3.9",
26
25
  "Programming Language :: Python :: 3.10",
27
26
  "Programming Language :: Python :: 3.11",
28
27
  "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Programming Language :: Python :: 3.14",
29
30
  "Topic :: Scientific/Engineering",
30
31
  "Topic :: Scientific/Engineering :: GIS",
31
32
  "Topic :: Software Development :: Libraries :: Python Modules"
@@ -42,16 +43,17 @@ Source = "https://github.com/Healy-Hyperspatial/sfeos-tools"
42
43
 
43
44
  [project.optional-dependencies]
44
45
  elasticsearch = [
45
- "stac-fastapi-elasticsearch",
46
+ "stac-fastapi-elasticsearch>=6.6.0",
46
47
  ]
47
48
  opensearch = [
48
- "stac-fastapi-opensearch",
49
+ "stac-fastapi-opensearch>=6.6.0",
49
50
  ]
50
51
  dev = [
51
- "stac-fastapi-elasticsearch",
52
- "stac-fastapi-opensearch",
52
+ "stac-fastapi-elasticsearch>=6.6.0",
53
+ "stac-fastapi-opensearch>=6.6.0",
53
54
  # Development tools
54
55
  "pytest>=6.0",
56
+ "pytest-asyncio>=0.21.0",
55
57
  "pytest-cov>=2.0",
56
58
  "black>=21.0",
57
59
  "isort>=5.0",
@@ -79,7 +81,11 @@ line_length = 88
79
81
  [tool.pytest.ini_options]
80
82
  testpaths = ["tests"]
81
83
  python_files = "test_*.py"
82
- addopts = "-v --cov=sfeos_tool --cov-report=term-missing"
84
+ addopts = "-v --cov=sfeos_tools --cov-report=term-missing"
85
+ asyncio_mode = "auto"
86
+ filterwarnings = [
87
+ "ignore::DeprecationWarning:pydantic._internal._config",
88
+ ]
83
89
 
84
90
  [tool.coverage.report]
85
91
  exclude_lines = [
@@ -0,0 +1,114 @@
1
+ """Bbox shape migration utilities for SFEOS collections."""
2
+
3
+ import logging
4
+
5
+ from stac_fastapi.sfeos_helpers.database import add_bbox_shape_to_collection
6
+ from stac_fastapi.sfeos_helpers.mappings import COLLECTIONS_INDEX
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ async def process_collection_bbox_shape(client, collection_doc, backend):
12
+ """Process a single collection document to add bbox_shape field.
13
+
14
+ Args:
15
+ client: Elasticsearch/OpenSearch client
16
+ collection_doc: Collection document from database
17
+ backend: Backend type ('elasticsearch' or 'opensearch')
18
+
19
+ Returns:
20
+ bool: True if collection was updated, False if no update was needed
21
+ """
22
+ collection = collection_doc["_source"]
23
+ collection_id = collection.get("id", collection_doc["_id"])
24
+
25
+ # Use the shared function to add bbox_shape
26
+ was_added = add_bbox_shape_to_collection(collection)
27
+
28
+ if not was_added:
29
+ return False
30
+
31
+ # Update the collection in the database
32
+ if backend == "elasticsearch":
33
+ await client.index(
34
+ index=COLLECTIONS_INDEX,
35
+ id=collection_id,
36
+ document=collection,
37
+ refresh=True,
38
+ )
39
+ else: # opensearch
40
+ await client.index(
41
+ index=COLLECTIONS_INDEX,
42
+ id=collection_id,
43
+ body=collection,
44
+ refresh=True,
45
+ )
46
+
47
+ logger.info(f"Collection '{collection_id}': Added bbox_shape field")
48
+ return True
49
+
50
+
51
+ async def run_add_bbox_shape(backend):
52
+ """Add bbox_shape field to all existing collections.
53
+
54
+ Args:
55
+ backend: Backend type ('elasticsearch' or 'opensearch')
56
+ """
57
+ import os
58
+
59
+ logger.info(
60
+ f"Starting migration: Adding bbox_shape to existing collections ({backend})"
61
+ )
62
+
63
+ # Log connection info (showing what will be used by the client)
64
+ es_host = os.getenv("ES_HOST", "localhost")
65
+ es_port = os.getenv(
66
+ "ES_PORT", "9200"
67
+ ) # Both backends default to 9200 in their config
68
+ es_use_ssl = os.getenv("ES_USE_SSL", "true")
69
+ logger.info(f"Connecting to {backend} at {es_host}:{es_port} (SSL: {es_use_ssl})")
70
+
71
+ # Create client based on backend
72
+ if backend == "elasticsearch":
73
+ from stac_fastapi.elasticsearch.config import AsyncElasticsearchSettings
74
+
75
+ settings = AsyncElasticsearchSettings()
76
+ else: # opensearch
77
+ from stac_fastapi.opensearch.config import AsyncOpensearchSettings
78
+
79
+ settings = AsyncOpensearchSettings()
80
+
81
+ client = settings.create_client
82
+
83
+ try:
84
+ # Get all collections
85
+ response = await client.search(
86
+ index=COLLECTIONS_INDEX,
87
+ body={
88
+ "query": {"match_all": {}},
89
+ "size": 10000,
90
+ }, # Adjust size if you have more collections
91
+ )
92
+
93
+ total_collections = response["hits"]["total"]["value"]
94
+ logger.info(f"Found {total_collections} collections to process")
95
+
96
+ updated_count = 0
97
+ skipped_count = 0
98
+
99
+ for hit in response["hits"]["hits"]:
100
+ was_updated = await process_collection_bbox_shape(client, hit, backend)
101
+ if was_updated:
102
+ updated_count += 1
103
+ else:
104
+ skipped_count += 1
105
+
106
+ logger.info(
107
+ f"Migration complete: {updated_count} collections updated, {skipped_count} skipped"
108
+ )
109
+
110
+ except Exception as e:
111
+ logger.error(f"Migration failed with error: {e}")
112
+ raise
113
+ finally:
114
+ await client.close()
@@ -13,121 +13,14 @@ import logging
13
13
  import sys
14
14
 
15
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
16
 
17
+ from .bbox_shape import run_add_bbox_shape
19
18
  from .reindex import run as unified_reindex_run
20
19
 
21
20
  logging.basicConfig(level=logging.INFO)
22
21
  logger = logging.getLogger(__name__)
23
22
 
24
23
 
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
24
  @click.group()
132
25
  @click.version_option(version="0.1.0", prog_name="sfeos-tools")
133
26
  def cli():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sfeos-tools
3
- Version: 0.1.0
3
+ Version: 0.1.1
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>
@@ -35,11 +35,12 @@ Classifier: Development Status :: 4 - Beta
35
35
  Classifier: Intended Audience :: Developers
36
36
  Classifier: Intended Audience :: Science/Research
37
37
  Classifier: License :: OSI Approved :: MIT License
38
- Classifier: Programming Language :: Python :: 3.8
39
38
  Classifier: Programming Language :: Python :: 3.9
40
39
  Classifier: Programming Language :: Python :: 3.10
41
40
  Classifier: Programming Language :: Python :: 3.11
42
41
  Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Programming Language :: Python :: 3.13
43
+ Classifier: Programming Language :: Python :: 3.14
43
44
  Classifier: Topic :: Scientific/Engineering
44
45
  Classifier: Topic :: Scientific/Engineering :: GIS
45
46
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
@@ -48,13 +49,14 @@ Description-Content-Type: text/markdown
48
49
  License-File: LICENSE
49
50
  Requires-Dist: click>=8.0.0
50
51
  Provides-Extra: elasticsearch
51
- Requires-Dist: stac-fastapi-elasticsearch; extra == "elasticsearch"
52
+ Requires-Dist: stac-fastapi-elasticsearch>=6.6.0; extra == "elasticsearch"
52
53
  Provides-Extra: opensearch
53
- Requires-Dist: stac-fastapi-opensearch; extra == "opensearch"
54
+ Requires-Dist: stac-fastapi-opensearch>=6.6.0; extra == "opensearch"
54
55
  Provides-Extra: dev
55
- Requires-Dist: stac-fastapi-elasticsearch; extra == "dev"
56
- Requires-Dist: stac-fastapi-opensearch; extra == "dev"
56
+ Requires-Dist: stac-fastapi-elasticsearch>=6.6.0; extra == "dev"
57
+ Requires-Dist: stac-fastapi-opensearch>=6.6.0; extra == "dev"
57
58
  Requires-Dist: pytest>=6.0; extra == "dev"
59
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
58
60
  Requires-Dist: pytest-cov>=2.0; extra == "dev"
59
61
  Requires-Dist: black>=21.0; extra == "dev"
60
62
  Requires-Dist: isort>=5.0; extra == "dev"
@@ -66,6 +68,22 @@ Dynamic: license-file
66
68
 
67
69
  CLI tools for managing [stac-fastapi-elasticsearch-opensearch](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch) deployments.
68
70
 
71
+ <!-- markdownlint-disable MD033 MD041 -->
72
+
73
+
74
+ <p align="left">
75
+ <img src="https://raw.githubusercontent.com/stac-utils/stac-fastapi-elasticsearch-opensearch/refs/heads/main/assets/sfeos.png" width=1000>
76
+ </p>
77
+
78
+ <!-- **Jump to:** [Project Introduction](#project-introduction---what-is-sfeos) | [Quick Start](#quick-start) | [Table of Contents](#table-of-contents) -->
79
+
80
+ [![Downloads](https://static.pepy.tech/badge/sfeos-tools?color=blue)](https://pepy.tech/project/sfeos-tools)
81
+ [![GitHub contributors](https://img.shields.io/github/contributors/healy-hyperspatial/sfeos-tools?color=blue)](https://github.com/healy-hyperspatial/sfeos-tools/graphs/contributors)
82
+ [![GitHub stars](https://img.shields.io/github/stars/healy-hyperspatial/sfeos-tools.svg?color=blue)](https://github.com/healy-hyperspatial/sfeos-tools/stargazers)
83
+ [![GitHub forks](https://img.shields.io/github/forks/healy-hyperspatial/sfeos-tools.svg?color=blue)](https://github.com/healy-hyperspatial/sfeos-tools/network/members)
84
+ [![PyPI version](https://img.shields.io/pypi/v/sfeos-tools.svg?color=blue)](https://pypi.org/project/sfeos-tools/)
85
+ [![STAC](https://img.shields.io/badge/STAC-1.1.0-blue.svg)](https://github.com/radiantearth/stac-spec/tree/v1.1.0)
86
+
69
87
  ## Table of Contents
70
88
 
71
89
  - [Installation](#installation)
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  sfeos_tools/__init__.py
5
+ sfeos_tools/bbox_shape.py
5
6
  sfeos_tools/cli.py
6
7
  sfeos_tools/reindex.py
7
8
  sfeos_tools.egg-info/PKG-INFO
@@ -9,4 +10,5 @@ sfeos_tools.egg-info/SOURCES.txt
9
10
  sfeos_tools.egg-info/dependency_links.txt
10
11
  sfeos_tools.egg-info/entry_points.txt
11
12
  sfeos_tools.egg-info/requires.txt
12
- sfeos_tools.egg-info/top_level.txt
13
+ sfeos_tools.egg-info/top_level.txt
14
+ tests/test_bbox_shape.py
@@ -0,0 +1,18 @@
1
+ click>=8.0.0
2
+
3
+ [dev]
4
+ stac-fastapi-elasticsearch>=6.6.0
5
+ stac-fastapi-opensearch>=6.6.0
6
+ pytest>=6.0
7
+ pytest-asyncio>=0.21.0
8
+ pytest-cov>=2.0
9
+ black>=21.0
10
+ isort>=5.0
11
+ mypy>=0.900
12
+ flake8>=4.0.0
13
+
14
+ [elasticsearch]
15
+ stac-fastapi-elasticsearch>=6.6.0
16
+
17
+ [opensearch]
18
+ stac-fastapi-opensearch>=6.6.0
@@ -0,0 +1,275 @@
1
+ """Tests for bbox_shape module."""
2
+
3
+ from unittest.mock import AsyncMock, MagicMock, patch
4
+
5
+ import pytest
6
+
7
+ from sfeos_tools.bbox_shape import process_collection_bbox_shape, run_add_bbox_shape
8
+
9
+
10
+ class TestProcessCollectionBboxShape:
11
+ """Tests for process_collection_bbox_shape function."""
12
+
13
+ @pytest.mark.asyncio
14
+ async def test_process_collection_bbox_shape_elasticsearch_updated(self):
15
+ """Test processing a collection with Elasticsearch backend when bbox_shape is added."""
16
+ mock_client = AsyncMock()
17
+ collection_doc = {
18
+ "_id": "test-collection",
19
+ "_source": {
20
+ "id": "test-collection",
21
+ "bbox": [[-180, -90, 180, 90]],
22
+ },
23
+ }
24
+
25
+ with patch(
26
+ "sfeos_tools.bbox_shape.add_bbox_shape_to_collection", return_value=True
27
+ ):
28
+ result = await process_collection_bbox_shape(
29
+ mock_client, collection_doc, "elasticsearch"
30
+ )
31
+
32
+ assert result is True
33
+ mock_client.index.assert_called_once()
34
+ call_kwargs = mock_client.index.call_args[1]
35
+ assert call_kwargs["index"] == "collections"
36
+ assert call_kwargs["id"] == "test-collection"
37
+ assert call_kwargs["refresh"] is True
38
+ assert "document" in call_kwargs
39
+
40
+ @pytest.mark.asyncio
41
+ async def test_process_collection_bbox_shape_opensearch_updated(self):
42
+ """Test processing a collection with OpenSearch backend when bbox_shape is added."""
43
+ mock_client = AsyncMock()
44
+ collection_doc = {
45
+ "_id": "test-collection",
46
+ "_source": {
47
+ "id": "test-collection",
48
+ "bbox": [[-180, -90, 180, 90]],
49
+ },
50
+ }
51
+
52
+ with patch(
53
+ "sfeos_tools.bbox_shape.add_bbox_shape_to_collection", return_value=True
54
+ ):
55
+ result = await process_collection_bbox_shape(
56
+ mock_client, collection_doc, "opensearch"
57
+ )
58
+
59
+ assert result is True
60
+ mock_client.index.assert_called_once()
61
+ call_kwargs = mock_client.index.call_args[1]
62
+ assert call_kwargs["index"] == "collections"
63
+ assert call_kwargs["id"] == "test-collection"
64
+ assert call_kwargs["refresh"] is True
65
+ assert "body" in call_kwargs
66
+
67
+ @pytest.mark.asyncio
68
+ async def test_process_collection_bbox_shape_not_updated(self):
69
+ """Test processing a collection when bbox_shape is not added."""
70
+ mock_client = AsyncMock()
71
+ collection_doc = {
72
+ "_id": "test-collection",
73
+ "_source": {
74
+ "id": "test-collection",
75
+ "bbox": [[-180, -90, 180, 90]],
76
+ },
77
+ }
78
+
79
+ with patch(
80
+ "sfeos_tools.bbox_shape.add_bbox_shape_to_collection", return_value=False
81
+ ):
82
+ result = await process_collection_bbox_shape(
83
+ mock_client, collection_doc, "elasticsearch"
84
+ )
85
+
86
+ assert result is False
87
+ mock_client.index.assert_not_called()
88
+
89
+ @pytest.mark.asyncio
90
+ async def test_process_collection_uses_id_from_source(self):
91
+ """Test that collection ID is extracted from _source when available."""
92
+ mock_client = AsyncMock()
93
+ collection_doc = {
94
+ "_id": "doc-id",
95
+ "_source": {
96
+ "id": "collection-id",
97
+ "bbox": [[-180, -90, 180, 90]],
98
+ },
99
+ }
100
+
101
+ with patch(
102
+ "sfeos_tools.bbox_shape.add_bbox_shape_to_collection", return_value=True
103
+ ):
104
+ await process_collection_bbox_shape(
105
+ mock_client, collection_doc, "elasticsearch"
106
+ )
107
+
108
+ call_kwargs = mock_client.index.call_args[1]
109
+ assert call_kwargs["id"] == "collection-id"
110
+
111
+ @pytest.mark.asyncio
112
+ async def test_process_collection_falls_back_to_doc_id(self):
113
+ """Test that collection ID falls back to _id when not in _source."""
114
+ mock_client = AsyncMock()
115
+ collection_doc = {
116
+ "_id": "doc-id",
117
+ "_source": {
118
+ "bbox": [[-180, -90, 180, 90]],
119
+ },
120
+ }
121
+
122
+ with patch(
123
+ "sfeos_tools.bbox_shape.add_bbox_shape_to_collection", return_value=True
124
+ ):
125
+ await process_collection_bbox_shape(
126
+ mock_client, collection_doc, "elasticsearch"
127
+ )
128
+
129
+ call_kwargs = mock_client.index.call_args[1]
130
+ assert call_kwargs["id"] == "doc-id"
131
+
132
+
133
+ class TestRunAddBboxShape:
134
+ """Tests for run_add_bbox_shape function."""
135
+
136
+ @pytest.mark.asyncio
137
+ async def test_run_add_bbox_shape_elasticsearch(self):
138
+ """Test run_add_bbox_shape with Elasticsearch backend."""
139
+ mock_client = AsyncMock()
140
+ mock_settings = MagicMock()
141
+ mock_settings.create_client = mock_client
142
+
143
+ mock_client.search.return_value = {
144
+ "hits": {
145
+ "total": {"value": 2},
146
+ "hits": [
147
+ {
148
+ "_id": "collection-1",
149
+ "_source": {
150
+ "id": "collection-1",
151
+ "bbox": [[-180, -90, 180, 90]],
152
+ },
153
+ },
154
+ {
155
+ "_id": "collection-2",
156
+ "_source": {
157
+ "id": "collection-2",
158
+ "bbox": [[-180, -90, 180, 90]],
159
+ },
160
+ },
161
+ ],
162
+ }
163
+ }
164
+
165
+ with patch(
166
+ "stac_fastapi.elasticsearch.config.AsyncElasticsearchSettings",
167
+ return_value=mock_settings,
168
+ ), patch(
169
+ "sfeos_tools.bbox_shape.add_bbox_shape_to_collection", return_value=True
170
+ ):
171
+ await run_add_bbox_shape("elasticsearch")
172
+
173
+ mock_client.search.assert_called_once()
174
+ assert mock_client.index.call_count == 2
175
+ mock_client.close.assert_called_once()
176
+
177
+ @pytest.mark.asyncio
178
+ async def test_run_add_bbox_shape_opensearch(self):
179
+ """Test run_add_bbox_shape with OpenSearch backend."""
180
+ mock_client = AsyncMock()
181
+ mock_settings = MagicMock()
182
+ mock_settings.create_client = mock_client
183
+
184
+ mock_client.search.return_value = {
185
+ "hits": {
186
+ "total": {"value": 1},
187
+ "hits": [
188
+ {
189
+ "_id": "collection-1",
190
+ "_source": {
191
+ "id": "collection-1",
192
+ "bbox": [[-180, -90, 180, 90]],
193
+ },
194
+ },
195
+ ],
196
+ }
197
+ }
198
+
199
+ with patch(
200
+ "stac_fastapi.opensearch.config.AsyncOpensearchSettings",
201
+ return_value=mock_settings,
202
+ ), patch(
203
+ "sfeos_tools.bbox_shape.add_bbox_shape_to_collection", return_value=True
204
+ ):
205
+ await run_add_bbox_shape("opensearch")
206
+
207
+ mock_client.search.assert_called_once()
208
+ mock_client.index.assert_called_once()
209
+ mock_client.close.assert_called_once()
210
+
211
+ @pytest.mark.asyncio
212
+ async def test_run_add_bbox_shape_handles_mixed_results(self):
213
+ """Test run_add_bbox_shape with some collections updated and some skipped."""
214
+ mock_client = AsyncMock()
215
+ mock_settings = MagicMock()
216
+ mock_settings.create_client = mock_client
217
+
218
+ mock_client.search.return_value = {
219
+ "hits": {
220
+ "total": {"value": 3},
221
+ "hits": [
222
+ {
223
+ "_id": "collection-1",
224
+ "_source": {
225
+ "id": "collection-1",
226
+ "bbox": [[-180, -90, 180, 90]],
227
+ },
228
+ },
229
+ {
230
+ "_id": "collection-2",
231
+ "_source": {
232
+ "id": "collection-2",
233
+ "bbox": [[-180, -90, 180, 90]],
234
+ },
235
+ },
236
+ {
237
+ "_id": "collection-3",
238
+ "_source": {
239
+ "id": "collection-3",
240
+ "bbox": [[-180, -90, 180, 90]],
241
+ },
242
+ },
243
+ ],
244
+ }
245
+ }
246
+
247
+ side_effects = [True, False, True]
248
+
249
+ with patch(
250
+ "stac_fastapi.elasticsearch.config.AsyncElasticsearchSettings",
251
+ return_value=mock_settings,
252
+ ), patch(
253
+ "sfeos_tools.bbox_shape.add_bbox_shape_to_collection",
254
+ side_effect=side_effects,
255
+ ):
256
+ await run_add_bbox_shape("elasticsearch")
257
+
258
+ assert mock_client.index.call_count == 2
259
+
260
+ @pytest.mark.asyncio
261
+ async def test_run_add_bbox_shape_closes_client_on_error(self):
262
+ """Test that client is closed even when an error occurs."""
263
+ mock_client = AsyncMock()
264
+ mock_settings = MagicMock()
265
+ mock_settings.create_client = mock_client
266
+
267
+ mock_client.search.side_effect = Exception("Connection error")
268
+
269
+ with patch(
270
+ "stac_fastapi.elasticsearch.config.AsyncElasticsearchSettings",
271
+ return_value=mock_settings,
272
+ ), pytest.raises(Exception, match="Connection error"):
273
+ await run_add_bbox_shape("elasticsearch")
274
+
275
+ mock_client.close.assert_called_once()
@@ -1,17 +0,0 @@
1
- click>=8.0.0
2
-
3
- [dev]
4
- stac-fastapi-elasticsearch
5
- stac-fastapi-opensearch
6
- pytest>=6.0
7
- pytest-cov>=2.0
8
- black>=21.0
9
- isort>=5.0
10
- mypy>=0.900
11
- flake8>=4.0.0
12
-
13
- [elasticsearch]
14
- stac-fastapi-elasticsearch
15
-
16
- [opensearch]
17
- stac-fastapi-opensearch
File without changes
File without changes