Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ jobs:
--ulimit nofile=65536:65536
--ulimit memlock=-1:-1

redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379

strategy:
matrix:
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
Expand Down Expand Up @@ -115,4 +125,7 @@ jobs:
ES_USE_SSL: false
DATABASE_REFRESH: true
ES_VERIFY_CERTS: false
BACKEND: ${{ matrix.backend == 'elasticsearch8' && 'elasticsearch' || 'opensearch' }}
REDIS_ENABLE: true
REDIS_HOST: localhost
REDIS_PORT: 6379
BACKEND: ${{ matrix.backend == 'elasticsearch8' && 'elasticsearch' || 'opensearch' }}
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ repos:
]
additional_dependencies: [
"types-attrs",
"types-requests"
"types-requests",
"types-redis"
]
- repo: https://github.com/PyCQA/pydocstyle
rev: 6.1.1
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added

- Environment variable `EXCLUDED_FROM_QUERYABLES` to exclude specific fields from queryables endpoint and filtering. Supports comma-separated list of fully qualified field names (e.g., `properties.auth:schemes,properties.storage:schemes`) [#489](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/489)
- Added Redis caching configuration for navigation pagination support, enabling proper `prev` and `next` links in paginated responses. [#488](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/488)

### Changed

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,4 @@ docs-image:
.PHONY: docs
docs: docs-image
docker compose -f compose.docs.yml \
run docs
run docs
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
- [Collection Pagination](#collection-pagination)
- [SFEOS Tools CLI](#sfeos-tools-cli)
- [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
- [Redis for navigation](#redis-for-navigation)
- [Elasticsearch Mappings](#elasticsearch-mappings)
- [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
- [Snapshots](#snapshots)
Expand Down Expand Up @@ -344,6 +345,32 @@ You can customize additional settings in your `.env` file:
> [!NOTE]
> 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.

## Redis for Navigation environment variables:
These Redis configuration variables to enable proper navigation functionality in STAC FastAPI.

| Variable | Description | Default | Required |
|-------------------------------|----------------------------------------------------------------------------------------------|--------------------------|---------------------------------------------------------------------------------------------|
| `REDIS_ENABLE` | Enables or disables Redis caching for navigation. Set to `true` to use Redis, or `false` to disable. | `false` | **Required** (determines whether Redis is used at all) |
| **Redis Sentinel** | | | |
| `REDIS_SENTINEL_HOSTS` | Comma-separated list of Redis Sentinel hostnames/IP addresses. | `""` | Conditional (required if using Sentinel) |
| `REDIS_SENTINEL_PORTS` | Comma-separated list of Redis Sentinel ports (must match order). | `"26379"` | Conditional (required if using Sentinel) |
| `REDIS_SENTINEL_MASTER_NAME` | Name of the Redis master node in Sentinel configuration. | `"master"` | Conditional (required if using Sentinel) |
| **Redis** | | | |
| `REDIS_HOST` | Redis server hostname or IP address for Redis configuration. | `""` | Conditional (required for standalone Redis) |
| `REDIS_PORT` | Redis server port for Redis configuration. | `6379` | Conditional (required for standalone Redis) |
| **Both** | | | |
| `REDIS_DB` | Redis database number to use for caching. | `0` (Sentinel) / `15` (Standalone) | Optional |
| `REDIS_MAX_CONNECTIONS` | Maximum number of connections in the Redis connection pool. | `10` | Optional |
| `REDIS_RETRY_TIMEOUT` | Enable retry on timeout for Redis operations. | `true` | Optional |
| `REDIS_DECODE_RESPONSES` | Automatically decode Redis responses to strings. | `true` | Optional |
| `REDIS_CLIENT_NAME` | Client name identifier for Redis connections. | `"stac-fastapi-app"` | Optional |
| `REDIS_HEALTH_CHECK_INTERVAL` | Interval in seconds for Redis health checks. | `30` | Optional |
| `REDIS_SELF_LINK_TTL` | Time-to-live (TTL) in seconds for storing self-links in Redis, used for pagination caching. | 1800 | Optional |


> [!NOTE]
> Use either the Sentinel configuration (`REDIS_SENTINEL_HOSTS`, `REDIS_SENTINEL_PORTS`, `REDIS_SENTINEL_MASTER_NAME`) OR the Redis configuration (`REDIS_HOST`, `REDIS_PORT`), but not both.

## Excluding Fields from Queryables

You can exclude specific fields from being exposed in the queryables endpoint and from filtering by setting the `EXCLUDED_FROM_QUERYABLES` environment variable. This is useful for hiding sensitive or internal fields that should not be queryable by API users.
Expand Down Expand Up @@ -615,6 +642,19 @@ The system uses a precise naming convention:
python3 data_loader.py --base-url http://localhost:8080 --use-bulk
```

## Redis for Navigation

The Redis cache stores navigation state for paginated results, allowing the system to maintain previous page links using tokens. The configuration supports both Redis Sentinel and standalone Redis setups.

Steps to configure:
1. Ensure that a Redis instance is available, either a standalone server or a Sentinel-managed cluster.
2. Establish a connection between STAC FastAPI and Redis instance by setting the appropriate [**environment variables**](#redis-for-navigation-environment-variables). These define the Redis host, port, authentication, and optional Sentinel settings.
3. Control whether Redis caching is activated using the `REDIS_ENABLE` environment variable to `True` or `False`.
4. Ensure the appropriate version of `Redis` is installed:
```
pip install stac-fastapi-elasticsearch[redis]
```

## Elasticsearch Mappings

- **Overview**: Mappings apply to search index, not source data. They define how documents and their fields are stored and indexed.
Expand Down
19 changes: 19 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ services:
- BACKEND=elasticsearch
- DATABASE_REFRESH=true
- ENABLE_COLLECTIONS_SEARCH_ROUTE=true
- REDIS_ENABLE=true
- REDIS_HOST=redis
- REDIS_PORT=6379
ports:
- "8080:8080"
volumes:
Expand All @@ -31,6 +34,7 @@ services:
- ./esdata:/usr/share/elasticsearch/data
depends_on:
- elasticsearch
- redis
command:
bash -c "./scripts/wait-for-it-es.sh es-container:9200 && python -m stac_fastapi.elasticsearch.app"

Expand Down Expand Up @@ -58,6 +62,9 @@ services:
- BACKEND=opensearch
- STAC_FASTAPI_RATE_LIMIT=200/minute
- ENABLE_COLLECTIONS_SEARCH_ROUTE=true
- REDIS_ENABLE=true
- REDIS_HOST=redis
- REDIS_PORT=6379
ports:
- "8082:8082"
volumes:
Expand All @@ -66,6 +73,7 @@ services:
- ./osdata:/usr/share/opensearch/data
depends_on:
- opensearch
- redis
command:
bash -c "./scripts/wait-for-it-es.sh os-container:9202 && python -m stac_fastapi.opensearch.app"

Expand Down Expand Up @@ -96,3 +104,14 @@ services:
- ./opensearch/snapshots:/usr/share/opensearch/snapshots
ports:
- "9202:9202"

redis:
image: redis:7-alpine
hostname: redis
ports:
- "6379:6379"
volumes:
- redis_test_data:/data
command: redis-server
volumes:
redis_test_data:
1 change: 1 addition & 0 deletions stac_fastapi/core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dependencies = [
"pygeofilter~=0.3.1",
"jsonschema~=4.0.0",
"slowapi~=0.1.9",
"redis==6.4.0",
]

[project.urls]
Expand Down
42 changes: 40 additions & 2 deletions stac_fastapi/core/stac_fastapi/core/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
from stac_fastapi.core.base_settings import ApiBaseSettings
from stac_fastapi.core.datetime_utils import format_datetime_range
from stac_fastapi.core.models.links import PagingLinks
from stac_fastapi.core.redis_utils import redis_pagination_links
from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
from stac_fastapi.core.session import Session
from stac_fastapi.core.utilities import filter_fields
from stac_fastapi.core.utilities import filter_fields, get_bool_env
from stac_fastapi.extensions.core.transaction import AsyncBaseTransactionsClient
from stac_fastapi.extensions.core.transaction.request import (
PartialCollection,
Expand Down Expand Up @@ -262,6 +263,7 @@ async def all_collections(
A Collections object containing all the collections in the database and links to various resources.
"""
base_url = str(request.base_url)
redis_enable = get_bool_env("REDIS_ENABLE", default=False)

global_max_limit = (
int(os.getenv("STAC_GLOBAL_COLLECTION_MAX_LIMIT"))
Expand Down Expand Up @@ -417,6 +419,14 @@ async def all_collections(
},
]

if redis_enable:
await redis_pagination_links(
current_url=str(request.url),
token=token,
next_token=next_token,
links=links,
)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code into a function

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonhealy1 I have moved this code into handle_pagination_links func that can be reused where needed.

if next_token:
next_link = PagingLinks(next=next_token, request=request).link_next()
links.append(next_link)
Expand Down Expand Up @@ -761,8 +771,8 @@ async def post_search(
search_request.limit = limit

base_url = str(request.base_url)

search = self.database.make_search()
redis_enable = get_bool_env("REDIS_ENABLE", default=False)

if search_request.ids:
search = self.database.apply_ids_filter(
Expand Down Expand Up @@ -866,6 +876,34 @@ async def post_search(
]
links = await PagingLinks(request=request, next=next_token).get_links()

collection_links = []
# Add "collection" and "parent" rels only for /collections/{collection_id}/items
if search_request.collections and "/items" in str(request.url):
for collection_id in search_request.collections:
collection_links.extend(
[
{
"rel": "collection",
"type": "application/json",
"href": urljoin(base_url, f"collections/{collection_id}"),
},
{
"rel": "parent",
"type": "application/json",
"href": urljoin(base_url, f"collections/{collection_id}"),
},
]
)
links.extend(collection_links)

if redis_enable:
await redis_pagination_links(
current_url=str(request.url),
token=token_param,
next_token=next_token,
links=links,
)

Copy link
Collaborator

@jonhealy1 jonhealy1 Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this code - the redis_enabled block - be put into a function? It is used with all_collections too

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code block was replaced here with func as well. Thank you!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change the function name for handle_pagination_links - also, mentioned below - and let's check the REDIS IS ENABLED here before we go to the function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thank you!

return stac_types.ItemCollection(
type="FeatureCollection",
features=items,
Expand Down
Loading