Skip to content

Commit 64bf85b

Browse files
Yuri ZmytrakovYuri Zmytrakov
authored andcommitted
fix: apply recommendations
1 parent e8d55c9 commit 64bf85b

File tree

8 files changed

+191
-25
lines changed

8 files changed

+191
-25
lines changed

.github/workflows/cicd.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ jobs:
6666
ports:
6767
- 9202:9202
6868

69+
redis:
70+
image: redis:7-alpine
71+
options: >-
72+
--health-cmd "redis-cli ping"
73+
--health-interval 10s
74+
--health-timeout 5s
75+
--health-retries 5
76+
ports:
77+
- 6379:6379
78+
6979
strategy:
7080
matrix:
7181
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13"]
@@ -126,3 +136,6 @@ jobs:
126136
DATABASE_REFRESH: true
127137
ES_VERIFY_CERTS: false
128138
BACKEND: ${{ matrix.backend == 'elasticsearch7' && 'elasticsearch' || matrix.backend == 'elasticsearch8' && 'elasticsearch' || 'opensearch' }}
139+
REDIS_ENABLE: true
140+
REDIS_HOST: localhost
141+
REDIS_PORT: 6379

Makefile

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,15 @@ docker-shell-os:
6363

6464
.PHONY: test-elasticsearch
6565
test-elasticsearch:
66-
-$(run_es) /bin/bash -c 'pip install redis==6.4.0 export && ./scripts/wait-for-it-es.sh elasticsearch:9200 && cd stac_fastapi/tests/ && pytest'
67-
docker compose down
66+
docker compose -f compose-redis.yml up -d
67+
-$(run_es) /bin/bash -c 'export REDIS_ENABLE=true REDIS_HOST=redis REDIS_PORT=6379 && ./scripts/wait-for-it-es.sh elasticsearch:9200 && cd stac_fastapi/tests/ && pytest'
68+
docker compose -f compose-redis.yml down
6869

69-
.PHONY: test-opensearch
70+
.PHONY: test-opensearch
7071
test-opensearch:
71-
-$(run_os) /bin/bash -c 'pip install redis==6.4.0 export && ./scripts/wait-for-it-es.sh opensearch:9202 && cd stac_fastapi/tests/ && pytest'
72-
docker compose down
72+
docker compose -f compose-redis.yml up -d
73+
-$(run_os) /bin/bash -c 'export REDIS_ENABLE=true REDIS_HOST=redis REDIS_PORT=6379 && ./scripts/wait-for-it-es.sh opensearch:9202 && cd stac_fastapi/tests/ && pytest'
74+
docker compose -f compose-redis.yml down
7375

7476
.PHONY: test-datetime-filtering-es
7577
test-datetime-filtering-es:
@@ -82,7 +84,7 @@ test-datetime-filtering-os:
8284
docker compose down
8385

8486
.PHONY: test
85-
test: test-elasticsearch test-datetime-filtering-es test-opensearch test-datetime-filtering-os
87+
test: test-elasticsearch test-datetime-filtering-es test-opensearch test-datetime-filtering-os test-redis-es test-redis-os
8688

8789
.PHONY: run-database-es
8890
run-database-es:
@@ -117,4 +119,16 @@ docs-image:
117119
.PHONY: docs
118120
docs: docs-image
119121
docker compose -f compose.docs.yml \
120-
run docs
122+
run docs
123+
124+
.PHONY: test-redis-es
125+
test-redis-es:
126+
docker compose -f compose-redis.yml up -d
127+
-$(run_es) /bin/bash -c 'export REDIS_ENABLE=true REDIS_HOST=redis REDIS_PORT=6379 && ./scripts/wait-for-it-es.sh elasticsearch:9200 && cd stac_fastapi/tests/ && pytest redis/ -v'
128+
docker compose -f compose-redis.yml down
129+
130+
.PHONY: test-redis-os
131+
test-redis-os:
132+
docker compose -f compose-redis.yml up -d
133+
-$(run_os) /bin/bash -c 'export REDIS_ENABLE=true REDIS_HOST=redis REDIS_PORT=6379 && ./scripts/wait-for-it-es.sh opensearch:9202 && cd stac_fastapi/tests/ && pytest redis/ -v'
134+
docker compose -f compose-redis.yml down

compose-redis.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: '3.8'
2+
3+
services:
4+
redis:
5+
image: redis:7-alpine
6+
ports:
7+
- "6379:6379"
8+
volumes:
9+
- redis_test_data:/data
10+
command: redis-server --appendonly yes
11+
12+
volumes:
13+
redis_test_data:

stac_fastapi/core/stac_fastapi/core/core.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,10 @@
2424
from stac_fastapi.core.base_settings import ApiBaseSettings
2525
from stac_fastapi.core.datetime_utils import format_datetime_range
2626
from stac_fastapi.core.models.links import PagingLinks
27-
from stac_fastapi.core.redis_utils import (
28-
connect_redis_sentinel,
29-
get_prev_link,
30-
save_self_link,
31-
)
27+
from stac_fastapi.core.redis_utils import connect_redis, get_prev_link, save_self_link
3228
from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
3329
from stac_fastapi.core.session import Session
34-
from stac_fastapi.core.utilities import filter_fields
30+
from stac_fastapi.core.utilities import filter_fields, get_bool_env
3531
from stac_fastapi.extensions.core.transaction import AsyncBaseTransactionsClient
3632
from stac_fastapi.extensions.core.transaction.request import (
3733
PartialCollection,
@@ -277,11 +273,13 @@ async def all_collections(
277273
sort = parsed_sort
278274

279275
current_url = str(request.url)
280-
redis = None
281-
try:
282-
redis = await connect_redis_sentinel()
283-
except Exception:
284-
redis = None
276+
redis_enable = get_bool_env("REDIS_ENABLE", default=False)
277+
278+
if redis_enable:
279+
try:
280+
redis = await connect_redis()
281+
except Exception:
282+
redis = None
285283

286284
# Convert q to a list if it's a string
287285
q_list = None
@@ -311,7 +309,7 @@ async def all_collections(
311309
},
312310
]
313311

314-
if redis:
312+
if redis_enable and redis:
315313
if next_token:
316314
await save_self_link(redis, next_token, current_url)
317315

@@ -557,10 +555,14 @@ async def post_search(
557555
HTTPException: If there is an error with the cql2_json filter.
558556
"""
559557
base_url = str(request.base_url)
560-
try:
561-
redis = await connect_redis_sentinel()
562-
except Exception:
563-
redis = None
558+
redis_enable = get_bool_env("REDIS_ENABLE", default=False)
559+
560+
redis = None
561+
if redis_enable:
562+
try:
563+
redis = await connect_redis()
564+
except Exception:
565+
redis = None
564566

565567
search = self.database.make_search()
566568

@@ -690,7 +692,7 @@ async def post_search(
690692
)
691693
links.extend(collection_links)
692694

693-
if redis:
695+
if redis_enable and redis:
694696
self_link = str(request.url)
695697
await save_self_link(redis, next_token, self_link)
696698

stac_fastapi/core/stac_fastapi/core/redis_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class RedisSettings(BaseSettings):
3939

4040

4141
# Select the Redis or Redis Sentinel configuration
42-
redis_settings: BaseSettings = RedisSentinelSettings()
42+
redis_settings: BaseSettings = RedisSettings()
4343

4444

4545
async def connect_redis(settings: Optional[RedisSettings] = None) -> aioredis.Redis:

stac_fastapi/tests/redis/__init__.py

Whitespace-only changes.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import uuid
2+
3+
import pytest
4+
5+
from ..conftest import create_collection, create_item
6+
7+
8+
@pytest.mark.asyncio
9+
async def test_search_pagination_uses_redis_cache(
10+
app_client, txn_client, load_test_data
11+
):
12+
"""Test Redis caching and navigation for the /search endpoint."""
13+
14+
collection = load_test_data("test_collection.json")
15+
collection_id = f"test-pagination-collection-{uuid.uuid4()}"
16+
collection["id"] = collection_id
17+
await create_collection(txn_client, collection)
18+
19+
for i in range(5):
20+
item = load_test_data("test_item.json")
21+
item["id"] = f"test-pagination-item-{uuid.uuid4()}"
22+
item["collection"] = collection_id
23+
await create_item(txn_client, item)
24+
25+
resp = await app_client.post(
26+
"/search", json={"collections": [collection_id], "limit": 1}
27+
)
28+
resp_json = resp.json()
29+
30+
next_link = next(
31+
(link for link in resp_json["links"] if link["rel"] == "next"), None
32+
)
33+
next_token = next_link["body"]["token"]
34+
35+
# Expect the previous link on the second page to be retrieved from Redis cache
36+
resp2 = await app_client.post(
37+
"/search",
38+
json={"collections": [collection_id], "limit": 1, "token": next_token},
39+
)
40+
resp2_json = resp2.json()
41+
42+
prev_link = next(
43+
(link for link in resp2_json["links"] if link["rel"] == "prev"), None
44+
)
45+
assert prev_link is not None
46+
47+
48+
@pytest.mark.asyncio
49+
async def test_collections_pagination_uses_redis_cache(
50+
app_client, txn_client, load_test_data
51+
):
52+
"""Test Redis caching and navigation for the /collection endpoint."""
53+
54+
collection_data = load_test_data("test_collection.json")
55+
for i in range(5):
56+
collection = collection_data.copy()
57+
collection["id"] = f"test-collection-pagination-{uuid.uuid4()}"
58+
collection["title"] = f"Test Collection Pagination {i}"
59+
await create_collection(txn_client, collection)
60+
61+
resp = await app_client.get("/collections", params={"limit": 1})
62+
assert resp.status_code == 200
63+
resp1_json = resp.json()
64+
65+
next_link = next(
66+
(link for link in resp1_json["links"] if link["rel"] == "next"), None
67+
)
68+
next_token = next_link["href"].split("token=")[1]
69+
70+
# Expect the previous link on the second page to be retrieved from Redis cache
71+
resp2 = await app_client.get(
72+
"/collections", params={"limit": 1, "token": next_token}
73+
)
74+
assert resp2.status_code == 200
75+
resp2_json = resp2.json()
76+
77+
prev_link = next(
78+
(link for link in resp2_json["links"] if link["rel"] == "prev"), None
79+
)
80+
assert prev_link is not None
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import pytest
2+
3+
from stac_fastapi.core.redis_utils import connect_redis, get_prev_link, save_self_link
4+
5+
6+
@pytest.mark.asyncio
7+
async def test_redis_connection():
8+
"""Test Redis connection."""
9+
redis = await connect_redis()
10+
assert redis is not None
11+
12+
# Test set/get
13+
await redis.set("string_key", "string_value")
14+
string_value = await redis.get("string_key")
15+
assert string_value == "string_value"
16+
17+
# Test key retrieval operation
18+
exists = await redis.exists("string_key")
19+
assert exists == 1
20+
21+
# Test key deletion
22+
await redis.delete("string_key")
23+
deleted_value = await redis.get("string_key")
24+
assert deleted_value is None
25+
26+
27+
@pytest.mark.asyncio
28+
async def test_redis_utils_functions():
29+
redis = await connect_redis()
30+
assert redis is not None
31+
32+
token = "test_token_123"
33+
self_link = "http://mywebsite.com/search?token=test_token_123"
34+
35+
await save_self_link(redis, token, self_link)
36+
retrieved_link = await get_prev_link(redis, token)
37+
assert retrieved_link == self_link
38+
39+
await save_self_link(redis, None, "should_not_save")
40+
null_result = await get_prev_link(redis, None)
41+
assert null_result is None
42+
43+
non_existent = await get_prev_link(redis, "non_existent_token")
44+
assert non_existent is None

0 commit comments

Comments
 (0)