mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 23:28:07 -05:00
feat(backend): Simplify caching to just store routes (#11140)
### Problem Limits caching to just the main marketplace routes ### Changes 🏗️ - **Simplified store cache implementation** in `backend/server/v2/store/cache.py` - Streamlined caching logic for better maintainability - Reduced complexity while maintaining performance - **Added cache invalidation on store updates** - Implemented cache clearing when new agents are added to the store - Added invalidation logic in admin store routes (`admin_store_routes.py`) - Ensures all pods reflect the latest store state after modifications - **Updated store database operations** in `backend/server/v2/store/db.py` - Modified to work with the new cache structure - **Added cache deletion tests** (`test_cache_delete.py`) - Validates cache invalidation works correctly - Ensures cache consistency across operations ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Verify store listings are cached correctly - [x] Upload a new agent to the store and confirm cache is invalidated
This commit is contained in:
@@ -7,6 +7,7 @@ import fastapi
|
||||
import fastapi.responses
|
||||
import prisma.enums
|
||||
|
||||
import backend.server.v2.store.cache as store_cache
|
||||
import backend.server.v2.store.db
|
||||
import backend.server.v2.store.model
|
||||
import backend.util.json
|
||||
@@ -86,6 +87,11 @@ async def review_submission(
|
||||
StoreSubmission with updated review information
|
||||
"""
|
||||
try:
|
||||
already_approved = (
|
||||
await backend.server.v2.store.db.check_submission_already_approved(
|
||||
store_listing_version_id=store_listing_version_id,
|
||||
)
|
||||
)
|
||||
submission = await backend.server.v2.store.db.review_store_submission(
|
||||
store_listing_version_id=store_listing_version_id,
|
||||
is_approved=request.is_approved,
|
||||
@@ -93,6 +99,11 @@ async def review_submission(
|
||||
internal_comments=request.internal_comments or "",
|
||||
reviewer_id=user_id,
|
||||
)
|
||||
|
||||
state_changed = already_approved != request.is_approved
|
||||
# Clear caches when the request is approved as it updates what is shown on the store
|
||||
if state_changed:
|
||||
store_cache.clear_all_caches()
|
||||
return submission
|
||||
except Exception as e:
|
||||
logger.exception("Error reviewing submission: %s", e)
|
||||
|
||||
76
autogpt_platform/backend/backend/server/v2/store/cache.py
Normal file
76
autogpt_platform/backend/backend/server/v2/store/cache.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from autogpt_libs.utils.cache import cached
|
||||
|
||||
import backend.server.v2.store.db
|
||||
|
||||
##############################################
|
||||
############### Caches #######################
|
||||
##############################################
|
||||
|
||||
|
||||
def clear_all_caches():
|
||||
"""Clear all caches."""
|
||||
_get_cached_store_agents.cache_clear()
|
||||
_get_cached_agent_details.cache_clear()
|
||||
_get_cached_store_creators.cache_clear()
|
||||
_get_cached_creator_details.cache_clear()
|
||||
|
||||
|
||||
# Cache store agents list for 5 minutes
|
||||
# Different cache entries for different query combinations
|
||||
@cached(maxsize=5000, ttl_seconds=300)
|
||||
async def _get_cached_store_agents(
|
||||
featured: bool,
|
||||
creator: str | None,
|
||||
sorted_by: str | None,
|
||||
search_query: str | None,
|
||||
category: str | None,
|
||||
page: int,
|
||||
page_size: int,
|
||||
):
|
||||
"""Cached helper to get store agents."""
|
||||
return await backend.server.v2.store.db.get_store_agents(
|
||||
featured=featured,
|
||||
creators=[creator] if creator else None,
|
||||
sorted_by=sorted_by,
|
||||
search_query=search_query,
|
||||
category=category,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
# Cache individual agent details for 15 minutes
|
||||
@cached(maxsize=200, ttl_seconds=300)
|
||||
async def _get_cached_agent_details(username: str, agent_name: str):
|
||||
"""Cached helper to get agent details."""
|
||||
return await backend.server.v2.store.db.get_store_agent_details(
|
||||
username=username, agent_name=agent_name
|
||||
)
|
||||
|
||||
|
||||
# Cache creators list for 5 minutes
|
||||
@cached(maxsize=200, ttl_seconds=300)
|
||||
async def _get_cached_store_creators(
|
||||
featured: bool,
|
||||
search_query: str | None,
|
||||
sorted_by: str | None,
|
||||
page: int,
|
||||
page_size: int,
|
||||
):
|
||||
"""Cached helper to get store creators."""
|
||||
return await backend.server.v2.store.db.get_store_creators(
|
||||
featured=featured,
|
||||
search_query=search_query,
|
||||
sorted_by=sorted_by,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
# Cache individual creator details for 5 minutes
|
||||
@cached(maxsize=100, ttl_seconds=300)
|
||||
async def _get_cached_creator_details(username: str):
|
||||
"""Cached helper to get creator details."""
|
||||
return await backend.server.v2.store.db.get_store_creator_details(
|
||||
username=username.lower()
|
||||
)
|
||||
@@ -1806,6 +1806,27 @@ async def get_admin_listings_with_versions(
|
||||
)
|
||||
|
||||
|
||||
async def check_submission_already_approved(
|
||||
store_listing_version_id: str,
|
||||
) -> bool:
|
||||
"""Check the submission status of a store listing version."""
|
||||
try:
|
||||
store_listing_version = (
|
||||
await prisma.models.StoreListingVersion.prisma().find_unique(
|
||||
where={"id": store_listing_version_id}
|
||||
)
|
||||
)
|
||||
if not store_listing_version:
|
||||
return False
|
||||
return (
|
||||
store_listing_version.submissionStatus
|
||||
== prisma.enums.SubmissionStatus.APPROVED
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking submission status: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def get_agent_as_admin(
|
||||
user_id: str | None,
|
||||
store_listing_version_id: str,
|
||||
|
||||
@@ -6,9 +6,9 @@ import urllib.parse
|
||||
import autogpt_libs.auth
|
||||
import fastapi
|
||||
import fastapi.responses
|
||||
from autogpt_libs.utils.cache import cached
|
||||
|
||||
import backend.data.graph
|
||||
import backend.server.v2.store.cache as store_cache
|
||||
import backend.server.v2.store.db
|
||||
import backend.server.v2.store.exceptions
|
||||
import backend.server.v2.store.image_gen
|
||||
@@ -21,117 +21,6 @@ logger = logging.getLogger(__name__)
|
||||
router = fastapi.APIRouter()
|
||||
|
||||
|
||||
##############################################
|
||||
############### Caches #######################
|
||||
##############################################
|
||||
|
||||
|
||||
# Cache user profiles for 1 hour per user
|
||||
@cached(maxsize=1000, ttl_seconds=3600)
|
||||
async def _get_cached_user_profile(user_id: str):
|
||||
"""Cached helper to get user profile."""
|
||||
return await backend.server.v2.store.db.get_user_profile(user_id)
|
||||
|
||||
|
||||
# Cache store agents list for 15 minutes
|
||||
# Different cache entries for different query combinations
|
||||
@cached(maxsize=5000, ttl_seconds=900)
|
||||
async def _get_cached_store_agents(
|
||||
featured: bool,
|
||||
creator: str | None,
|
||||
sorted_by: str | None,
|
||||
search_query: str | None,
|
||||
category: str | None,
|
||||
page: int,
|
||||
page_size: int,
|
||||
):
|
||||
"""Cached helper to get store agents."""
|
||||
return await backend.server.v2.store.db.get_store_agents(
|
||||
featured=featured,
|
||||
creators=[creator] if creator else None,
|
||||
sorted_by=sorted_by,
|
||||
search_query=search_query,
|
||||
category=category,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
# Cache individual agent details for 15 minutes
|
||||
@cached(maxsize=200, ttl_seconds=900)
|
||||
async def _get_cached_agent_details(username: str, agent_name: str):
|
||||
"""Cached helper to get agent details."""
|
||||
return await backend.server.v2.store.db.get_store_agent_details(
|
||||
username=username, agent_name=agent_name
|
||||
)
|
||||
|
||||
|
||||
# Cache agent graphs for 1 hour
|
||||
@cached(maxsize=200, ttl_seconds=3600)
|
||||
async def _get_cached_agent_graph(store_listing_version_id: str):
|
||||
"""Cached helper to get agent graph."""
|
||||
return await backend.server.v2.store.db.get_available_graph(
|
||||
store_listing_version_id
|
||||
)
|
||||
|
||||
|
||||
# Cache agent by version for 1 hour
|
||||
@cached(maxsize=200, ttl_seconds=3600)
|
||||
async def _get_cached_store_agent_by_version(store_listing_version_id: str):
|
||||
"""Cached helper to get store agent by version ID."""
|
||||
return await backend.server.v2.store.db.get_store_agent_by_version_id(
|
||||
store_listing_version_id
|
||||
)
|
||||
|
||||
|
||||
# Cache creators list for 1 hour
|
||||
@cached(maxsize=200, ttl_seconds=3600)
|
||||
async def _get_cached_store_creators(
|
||||
featured: bool,
|
||||
search_query: str | None,
|
||||
sorted_by: str | None,
|
||||
page: int,
|
||||
page_size: int,
|
||||
):
|
||||
"""Cached helper to get store creators."""
|
||||
return await backend.server.v2.store.db.get_store_creators(
|
||||
featured=featured,
|
||||
search_query=search_query,
|
||||
sorted_by=sorted_by,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
# Cache individual creator details for 1 hour
|
||||
@cached(maxsize=100, ttl_seconds=3600)
|
||||
async def _get_cached_creator_details(username: str):
|
||||
"""Cached helper to get creator details."""
|
||||
return await backend.server.v2.store.db.get_store_creator_details(
|
||||
username=username.lower()
|
||||
)
|
||||
|
||||
|
||||
# Cache user's own agents for 5 mins (shorter TTL as this changes more frequently)
|
||||
@cached(maxsize=500, ttl_seconds=300)
|
||||
async def _get_cached_my_agents(user_id: str, page: int, page_size: int):
|
||||
"""Cached helper to get user's agents."""
|
||||
return await backend.server.v2.store.db.get_my_agents(
|
||||
user_id, page=page, page_size=page_size
|
||||
)
|
||||
|
||||
|
||||
# Cache user's submissions for 1 hour (shorter TTL as this changes frequently)
|
||||
@cached(maxsize=500, ttl_seconds=3600)
|
||||
async def _get_cached_submissions(user_id: str, page: int, page_size: int):
|
||||
"""Cached helper to get user's submissions."""
|
||||
return await backend.server.v2.store.db.get_store_submissions(
|
||||
user_id=user_id,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
##############################################
|
||||
############### Profile Endpoints ############
|
||||
##############################################
|
||||
@@ -152,7 +41,7 @@ async def get_profile(
|
||||
Cached for 1 hour per user.
|
||||
"""
|
||||
try:
|
||||
profile = await _get_cached_user_profile(user_id)
|
||||
profile = await backend.server.v2.store.db.get_user_profile(user_id)
|
||||
if profile is None:
|
||||
return fastapi.responses.JSONResponse(
|
||||
status_code=404,
|
||||
@@ -198,8 +87,6 @@ async def update_or_create_profile(
|
||||
updated_profile = await backend.server.v2.store.db.update_profile(
|
||||
user_id=user_id, profile=profile
|
||||
)
|
||||
# Clear the cache for this user after profile update
|
||||
_get_cached_user_profile.cache_delete(user_id)
|
||||
return updated_profile
|
||||
except Exception as e:
|
||||
logger.exception("Failed to update profile for user %s: %s", user_id, e)
|
||||
@@ -234,7 +121,6 @@ async def get_agents(
|
||||
):
|
||||
"""
|
||||
Get a paginated list of agents from the store with optional filtering and sorting.
|
||||
Results are cached for 15 minutes.
|
||||
|
||||
Args:
|
||||
featured (bool, optional): Filter to only show featured agents. Defaults to False.
|
||||
@@ -270,7 +156,7 @@ async def get_agents(
|
||||
)
|
||||
|
||||
try:
|
||||
agents = await _get_cached_store_agents(
|
||||
agents = await store_cache._get_cached_store_agents(
|
||||
featured=featured,
|
||||
creator=creator,
|
||||
sorted_by=sorted_by,
|
||||
@@ -300,7 +186,6 @@ async def get_agents(
|
||||
async def get_agent(username: str, agent_name: str):
|
||||
"""
|
||||
This is only used on the AgentDetails Page.
|
||||
Results are cached for 15 minutes.
|
||||
|
||||
It returns the store listing agents details.
|
||||
"""
|
||||
@@ -308,7 +193,7 @@ async def get_agent(username: str, agent_name: str):
|
||||
username = urllib.parse.unquote(username).lower()
|
||||
# URL decode the agent name since it comes from the URL path
|
||||
agent_name = urllib.parse.unquote(agent_name).lower()
|
||||
agent = await _get_cached_agent_details(
|
||||
agent = await store_cache._get_cached_agent_details(
|
||||
username=username, agent_name=agent_name
|
||||
)
|
||||
return agent
|
||||
@@ -331,10 +216,11 @@ async def get_agent(username: str, agent_name: str):
|
||||
async def get_graph_meta_by_store_listing_version_id(store_listing_version_id: str):
|
||||
"""
|
||||
Get Agent Graph from Store Listing Version ID.
|
||||
Results are cached for 1 hour.
|
||||
"""
|
||||
try:
|
||||
graph = await _get_cached_agent_graph(store_listing_version_id)
|
||||
graph = await backend.server.v2.store.db.get_available_graph(
|
||||
store_listing_version_id
|
||||
)
|
||||
return graph
|
||||
except Exception:
|
||||
logger.exception("Exception occurred whilst getting agent graph")
|
||||
@@ -354,10 +240,12 @@ async def get_graph_meta_by_store_listing_version_id(store_listing_version_id: s
|
||||
async def get_store_agent(store_listing_version_id: str):
|
||||
"""
|
||||
Get Store Agent Details from Store Listing Version ID.
|
||||
Results are cached for 1 hour.
|
||||
"""
|
||||
try:
|
||||
agent = await _get_cached_store_agent_by_version(store_listing_version_id)
|
||||
agent = await backend.server.v2.store.db.get_store_agent_by_version_id(
|
||||
store_listing_version_id
|
||||
)
|
||||
|
||||
return agent
|
||||
except Exception:
|
||||
logger.exception("Exception occurred whilst getting store agent")
|
||||
@@ -435,8 +323,6 @@ async def get_creators(
|
||||
- Home Page Featured Creators
|
||||
- Search Results Page
|
||||
|
||||
Results are cached for 1 hour.
|
||||
|
||||
---
|
||||
|
||||
To support this functionality we need:
|
||||
@@ -455,7 +341,7 @@ async def get_creators(
|
||||
)
|
||||
|
||||
try:
|
||||
creators = await _get_cached_store_creators(
|
||||
creators = await store_cache._get_cached_store_creators(
|
||||
featured=featured,
|
||||
search_query=search_query,
|
||||
sorted_by=sorted_by,
|
||||
@@ -482,12 +368,11 @@ async def get_creator(
|
||||
):
|
||||
"""
|
||||
Get the details of a creator.
|
||||
Results are cached for 1 hour.
|
||||
- Creator Details Page
|
||||
"""
|
||||
try:
|
||||
username = urllib.parse.unquote(username).lower()
|
||||
creator = await _get_cached_creator_details(username=username)
|
||||
creator = await store_cache._get_cached_creator_details(username=username)
|
||||
return creator
|
||||
except Exception:
|
||||
logger.exception("Exception occurred whilst getting creator details")
|
||||
@@ -518,10 +403,11 @@ async def get_my_agents(
|
||||
):
|
||||
"""
|
||||
Get user's own agents.
|
||||
Results are cached for 5 minutes per user.
|
||||
"""
|
||||
try:
|
||||
agents = await _get_cached_my_agents(user_id, page=page, page_size=page_size)
|
||||
agents = await backend.server.v2.store.db.get_my_agents(
|
||||
user_id, page=page, page_size=page_size
|
||||
)
|
||||
return agents
|
||||
except Exception:
|
||||
logger.exception("Exception occurred whilst getting my agents")
|
||||
@@ -558,13 +444,6 @@ async def delete_submission(
|
||||
submission_id=submission_id,
|
||||
)
|
||||
|
||||
# Clear submissions cache for this specific user after deletion
|
||||
if result:
|
||||
# Clear user's own agents cache - we don't know all page/size combinations
|
||||
for page in range(1, 20):
|
||||
# Clear user's submissions cache for common defaults
|
||||
_get_cached_submissions.cache_delete(user_id, page=page, page_size=20)
|
||||
|
||||
return result
|
||||
except Exception:
|
||||
logger.exception("Exception occurred whilst deleting store submission")
|
||||
@@ -588,7 +467,6 @@ async def get_submissions(
|
||||
):
|
||||
"""
|
||||
Get a paginated list of store submissions for the authenticated user.
|
||||
Results are cached for 1 hour per user.
|
||||
|
||||
Args:
|
||||
user_id (str): ID of the authenticated user
|
||||
@@ -611,8 +489,10 @@ async def get_submissions(
|
||||
status_code=422, detail="Page size must be greater than 0"
|
||||
)
|
||||
try:
|
||||
listings = await _get_cached_submissions(
|
||||
user_id, page=page, page_size=page_size
|
||||
listings = await backend.server.v2.store.db.get_store_submissions(
|
||||
user_id=user_id,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
return listings
|
||||
except Exception:
|
||||
@@ -666,11 +546,6 @@ async def create_submission(
|
||||
recommended_schedule_cron=submission_request.recommended_schedule_cron,
|
||||
)
|
||||
|
||||
# Clear user's own agents cache - we don't know all page/size combinations
|
||||
for page in range(1, 20):
|
||||
# Clear user's submissions cache for common defaults
|
||||
_get_cached_submissions.cache_delete(user_id, page=page, page_size=20)
|
||||
|
||||
return result
|
||||
except Exception:
|
||||
logger.exception("Exception occurred whilst creating store submission")
|
||||
@@ -720,11 +595,6 @@ async def edit_submission(
|
||||
recommended_schedule_cron=submission_request.recommended_schedule_cron,
|
||||
)
|
||||
|
||||
# Clear user's own agents cache - we don't know all page/size combinations
|
||||
for page in range(1, 20):
|
||||
# Clear user's submissions cache for common defaults
|
||||
_get_cached_submissions.cache_delete(user_id, page=page, page_size=20)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -917,15 +787,10 @@ async def get_cache_metrics():
|
||||
)
|
||||
|
||||
# Add metrics for each cache
|
||||
add_cache_metrics("user_profile", _get_cached_user_profile)
|
||||
add_cache_metrics("store_agents", _get_cached_store_agents)
|
||||
add_cache_metrics("agent_details", _get_cached_agent_details)
|
||||
add_cache_metrics("agent_graph", _get_cached_agent_graph)
|
||||
add_cache_metrics("agent_by_version", _get_cached_store_agent_by_version)
|
||||
add_cache_metrics("store_creators", _get_cached_store_creators)
|
||||
add_cache_metrics("creator_details", _get_cached_creator_details)
|
||||
add_cache_metrics("my_agents", _get_cached_my_agents)
|
||||
add_cache_metrics("submissions", _get_cached_submissions)
|
||||
add_cache_metrics("store_agents", store_cache._get_cached_store_agents)
|
||||
add_cache_metrics("agent_details", store_cache._get_cached_agent_details)
|
||||
add_cache_metrics("store_creators", store_cache._get_cached_store_creators)
|
||||
add_cache_metrics("creator_details", store_cache._get_cached_creator_details)
|
||||
|
||||
# Add metadata/help text at the beginning
|
||||
prometheus_output = [
|
||||
|
||||
@@ -4,18 +4,12 @@ Test suite for verifying cache_delete functionality in store routes.
|
||||
Tests that specific cache entries can be deleted while preserving others.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.server.v2.store import routes
|
||||
from backend.server.v2.store.model import (
|
||||
ProfileDetails,
|
||||
StoreAgent,
|
||||
StoreAgentDetails,
|
||||
StoreAgentsResponse,
|
||||
)
|
||||
from backend.server.v2.store import cache as store_cache
|
||||
from backend.server.v2.store.model import StoreAgent, StoreAgentsResponse
|
||||
from backend.util.models import Pagination
|
||||
|
||||
|
||||
@@ -54,10 +48,10 @@ class TestCacheDeletion:
|
||||
return_value=mock_response,
|
||||
) as mock_db:
|
||||
# Clear cache first
|
||||
routes._get_cached_store_agents.cache_clear()
|
||||
store_cache._get_cached_store_agents.cache_clear()
|
||||
|
||||
# First call - should hit database
|
||||
result1 = await routes._get_cached_store_agents(
|
||||
result1 = await store_cache._get_cached_store_agents(
|
||||
featured=False,
|
||||
creator=None,
|
||||
sorted_by=None,
|
||||
@@ -70,7 +64,7 @@ class TestCacheDeletion:
|
||||
assert result1.agents[0].agent_name == "Test Agent"
|
||||
|
||||
# Second call with same params - should use cache
|
||||
await routes._get_cached_store_agents(
|
||||
await store_cache._get_cached_store_agents(
|
||||
featured=False,
|
||||
creator=None,
|
||||
sorted_by=None,
|
||||
@@ -82,7 +76,7 @@ class TestCacheDeletion:
|
||||
assert mock_db.call_count == 1 # No additional DB call
|
||||
|
||||
# Third call with different params - should hit database
|
||||
await routes._get_cached_store_agents(
|
||||
await store_cache._get_cached_store_agents(
|
||||
featured=True, # Different param
|
||||
creator=None,
|
||||
sorted_by=None,
|
||||
@@ -94,7 +88,7 @@ class TestCacheDeletion:
|
||||
assert mock_db.call_count == 2 # New DB call
|
||||
|
||||
# Delete specific cache entry
|
||||
deleted = routes._get_cached_store_agents.cache_delete(
|
||||
deleted = store_cache._get_cached_store_agents.cache_delete(
|
||||
featured=False,
|
||||
creator=None,
|
||||
sorted_by=None,
|
||||
@@ -106,7 +100,7 @@ class TestCacheDeletion:
|
||||
assert deleted is True # Entry was deleted
|
||||
|
||||
# Try to delete non-existent entry
|
||||
deleted = routes._get_cached_store_agents.cache_delete(
|
||||
deleted = store_cache._get_cached_store_agents.cache_delete(
|
||||
featured=False,
|
||||
creator="nonexistent",
|
||||
sorted_by=None,
|
||||
@@ -118,7 +112,7 @@ class TestCacheDeletion:
|
||||
assert deleted is False # Entry didn't exist
|
||||
|
||||
# Call with deleted params - should hit database again
|
||||
await routes._get_cached_store_agents(
|
||||
await store_cache._get_cached_store_agents(
|
||||
featured=False,
|
||||
creator=None,
|
||||
sorted_by=None,
|
||||
@@ -130,7 +124,7 @@ class TestCacheDeletion:
|
||||
assert mock_db.call_count == 3 # New DB call after deletion
|
||||
|
||||
# Call with featured=True - should still be cached
|
||||
await routes._get_cached_store_agents(
|
||||
await store_cache._get_cached_store_agents(
|
||||
featured=True,
|
||||
creator=None,
|
||||
sorted_by=None,
|
||||
@@ -141,105 +135,11 @@ class TestCacheDeletion:
|
||||
)
|
||||
assert mock_db.call_count == 3 # No additional DB call
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_details_cache_delete(self):
|
||||
"""Test that specific agent details cache entries can be deleted."""
|
||||
mock_response = StoreAgentDetails(
|
||||
store_listing_version_id="version1",
|
||||
slug="test-agent",
|
||||
agent_name="Test Agent",
|
||||
agent_video="https://example.com/video.mp4",
|
||||
agent_image=["https://example.com/image.jpg"],
|
||||
creator="testuser",
|
||||
creator_avatar="https://example.com/avatar.jpg",
|
||||
sub_heading="Test subheading",
|
||||
description="Test description",
|
||||
categories=["productivity"],
|
||||
runs=100,
|
||||
rating=4.5,
|
||||
versions=[],
|
||||
last_updated=datetime.datetime(2024, 1, 1),
|
||||
)
|
||||
|
||||
with patch(
|
||||
"backend.server.v2.store.db.get_store_agent_details",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_response,
|
||||
) as mock_db:
|
||||
# Clear cache first
|
||||
routes._get_cached_agent_details.cache_clear()
|
||||
|
||||
# First call - should hit database
|
||||
await routes._get_cached_agent_details(
|
||||
username="testuser", agent_name="testagent"
|
||||
)
|
||||
assert mock_db.call_count == 1
|
||||
|
||||
# Second call - should use cache
|
||||
await routes._get_cached_agent_details(
|
||||
username="testuser", agent_name="testagent"
|
||||
)
|
||||
assert mock_db.call_count == 1 # No additional DB call
|
||||
|
||||
# Delete specific entry
|
||||
deleted = routes._get_cached_agent_details.cache_delete(
|
||||
username="testuser", agent_name="testagent"
|
||||
)
|
||||
assert deleted is True
|
||||
|
||||
# Call again - should hit database
|
||||
await routes._get_cached_agent_details(
|
||||
username="testuser", agent_name="testagent"
|
||||
)
|
||||
assert mock_db.call_count == 2 # New DB call after deletion
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_profile_cache_delete(self):
|
||||
"""Test that user profile cache entries can be deleted."""
|
||||
mock_response = ProfileDetails(
|
||||
name="Test User",
|
||||
username="testuser",
|
||||
description="Test profile",
|
||||
links=["https://example.com"],
|
||||
)
|
||||
|
||||
with patch(
|
||||
"backend.server.v2.store.db.get_user_profile",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_response,
|
||||
) as mock_db:
|
||||
# Clear cache first
|
||||
routes._get_cached_user_profile.cache_clear()
|
||||
|
||||
# First call - should hit database
|
||||
await routes._get_cached_user_profile("user123")
|
||||
assert mock_db.call_count == 1
|
||||
|
||||
# Second call - should use cache
|
||||
await routes._get_cached_user_profile("user123")
|
||||
assert mock_db.call_count == 1
|
||||
|
||||
# Different user - should hit database
|
||||
await routes._get_cached_user_profile("user456")
|
||||
assert mock_db.call_count == 2
|
||||
|
||||
# Delete specific user's cache
|
||||
deleted = routes._get_cached_user_profile.cache_delete("user123")
|
||||
assert deleted is True
|
||||
|
||||
# user123 should hit database again
|
||||
await routes._get_cached_user_profile("user123")
|
||||
assert mock_db.call_count == 3
|
||||
|
||||
# user456 should still be cached
|
||||
await routes._get_cached_user_profile("user456")
|
||||
assert mock_db.call_count == 3 # No additional DB call
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cache_info_after_deletions(self):
|
||||
"""Test that cache_info correctly reflects deletions."""
|
||||
# Clear all caches first
|
||||
routes._get_cached_store_agents.cache_clear()
|
||||
store_cache._get_cached_store_agents.cache_clear()
|
||||
|
||||
mock_response = StoreAgentsResponse(
|
||||
agents=[],
|
||||
@@ -258,7 +158,7 @@ class TestCacheDeletion:
|
||||
):
|
||||
# Add multiple entries
|
||||
for i in range(5):
|
||||
await routes._get_cached_store_agents(
|
||||
await store_cache._get_cached_store_agents(
|
||||
featured=False,
|
||||
creator=f"creator{i}",
|
||||
sorted_by=None,
|
||||
@@ -269,12 +169,12 @@ class TestCacheDeletion:
|
||||
)
|
||||
|
||||
# Check cache size
|
||||
info = routes._get_cached_store_agents.cache_info()
|
||||
info = store_cache._get_cached_store_agents.cache_info()
|
||||
assert info["size"] == 5
|
||||
|
||||
# Delete some entries
|
||||
for i in range(2):
|
||||
deleted = routes._get_cached_store_agents.cache_delete(
|
||||
deleted = store_cache._get_cached_store_agents.cache_delete(
|
||||
featured=False,
|
||||
creator=f"creator{i}",
|
||||
sorted_by=None,
|
||||
@@ -286,7 +186,7 @@ class TestCacheDeletion:
|
||||
assert deleted is True
|
||||
|
||||
# Check cache size after deletion
|
||||
info = routes._get_cached_store_agents.cache_info()
|
||||
info = store_cache._get_cached_store_agents.cache_info()
|
||||
assert info["size"] == 3
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -307,10 +207,10 @@ class TestCacheDeletion:
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_response,
|
||||
) as mock_db:
|
||||
routes._get_cached_store_agents.cache_clear()
|
||||
store_cache._get_cached_store_agents.cache_clear()
|
||||
|
||||
# Test with all parameters
|
||||
await routes._get_cached_store_agents(
|
||||
await store_cache._get_cached_store_agents(
|
||||
featured=True,
|
||||
creator="testuser",
|
||||
sorted_by="rating",
|
||||
@@ -322,7 +222,7 @@ class TestCacheDeletion:
|
||||
assert mock_db.call_count == 1
|
||||
|
||||
# Delete with exact same parameters
|
||||
deleted = routes._get_cached_store_agents.cache_delete(
|
||||
deleted = store_cache._get_cached_store_agents.cache_delete(
|
||||
featured=True,
|
||||
creator="testuser",
|
||||
sorted_by="rating",
|
||||
@@ -334,7 +234,7 @@ class TestCacheDeletion:
|
||||
assert deleted is True
|
||||
|
||||
# Try to delete with slightly different parameters
|
||||
deleted = routes._get_cached_store_agents.cache_delete(
|
||||
deleted = store_cache._get_cached_store_agents.cache_delete(
|
||||
featured=True,
|
||||
creator="testuser",
|
||||
sorted_by="rating",
|
||||
|
||||
@@ -2553,7 +2553,7 @@
|
||||
"get": {
|
||||
"tags": ["v2", "store", "public"],
|
||||
"summary": "List store agents",
|
||||
"description": "Get a paginated list of agents from the store with optional filtering and sorting.\nResults are cached for 15 minutes.\n\nArgs:\n featured (bool, optional): Filter to only show featured agents. Defaults to False.\n creator (str | None, optional): Filter agents by creator username. Defaults to None.\n sorted_by (str | None, optional): Sort agents by \"runs\" or \"rating\". Defaults to None.\n search_query (str | None, optional): Search agents by name, subheading and description. Defaults to None.\n category (str | None, optional): Filter agents by category. Defaults to None.\n page (int, optional): Page number for pagination. Defaults to 1.\n page_size (int, optional): Number of agents per page. Defaults to 20.\n\nReturns:\n StoreAgentsResponse: Paginated list of agents matching the filters\n\nRaises:\n HTTPException: If page or page_size are less than 1\n\nUsed for:\n- Home Page Featured Agents\n- Home Page Top Agents\n- Search Results\n- Agent Details - Other Agents By Creator\n- Agent Details - Similar Agents\n- Creator Details - Agents By Creator",
|
||||
"description": "Get a paginated list of agents from the store with optional filtering and sorting.\n\nArgs:\n featured (bool, optional): Filter to only show featured agents. Defaults to False.\n creator (str | None, optional): Filter agents by creator username. Defaults to None.\n sorted_by (str | None, optional): Sort agents by \"runs\" or \"rating\". Defaults to None.\n search_query (str | None, optional): Search agents by name, subheading and description. Defaults to None.\n category (str | None, optional): Filter agents by category. Defaults to None.\n page (int, optional): Page number for pagination. Defaults to 1.\n page_size (int, optional): Number of agents per page. Defaults to 20.\n\nReturns:\n StoreAgentsResponse: Paginated list of agents matching the filters\n\nRaises:\n HTTPException: If page or page_size are less than 1\n\nUsed for:\n- Home Page Featured Agents\n- Home Page Top Agents\n- Search Results\n- Agent Details - Other Agents By Creator\n- Agent Details - Similar Agents\n- Creator Details - Agents By Creator",
|
||||
"operationId": "getV2List store agents",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -2639,7 +2639,7 @@
|
||||
"get": {
|
||||
"tags": ["v2", "store", "public"],
|
||||
"summary": "Get specific agent",
|
||||
"description": "This is only used on the AgentDetails Page.\nResults are cached for 15 minutes.\n\nIt returns the store listing agents details.",
|
||||
"description": "This is only used on the AgentDetails Page.\n\nIt returns the store listing agents details.",
|
||||
"operationId": "getV2Get specific agent",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -2679,7 +2679,7 @@
|
||||
"get": {
|
||||
"tags": ["v2", "store"],
|
||||
"summary": "Get agent graph",
|
||||
"description": "Get Agent Graph from Store Listing Version ID.\nResults are cached for 1 hour.",
|
||||
"description": "Get Agent Graph from Store Listing Version ID.",
|
||||
"operationId": "getV2Get agent graph",
|
||||
"security": [{ "HTTPBearerJWT": [] }],
|
||||
"parameters": [
|
||||
@@ -2713,7 +2713,7 @@
|
||||
"get": {
|
||||
"tags": ["v2", "store"],
|
||||
"summary": "Get agent by version",
|
||||
"description": "Get Store Agent Details from Store Listing Version ID.\nResults are cached for 1 hour.",
|
||||
"description": "Get Store Agent Details from Store Listing Version ID.",
|
||||
"operationId": "getV2Get agent by version",
|
||||
"security": [{ "HTTPBearerJWT": [] }],
|
||||
"parameters": [
|
||||
@@ -2803,7 +2803,7 @@
|
||||
"get": {
|
||||
"tags": ["v2", "store", "public"],
|
||||
"summary": "List store creators",
|
||||
"description": "This is needed for:\n- Home Page Featured Creators\n- Search Results Page\n\nResults are cached for 1 hour.\n\n---\n\nTo support this functionality we need:\n- featured: bool - to limit the list to just featured agents\n- search_query: str - vector search based on the creators profile description.\n- sorted_by: [agent_rating, agent_runs] -",
|
||||
"description": "This is needed for:\n- Home Page Featured Creators\n- Search Results Page\n\n---\n\nTo support this functionality we need:\n- featured: bool - to limit the list to just featured agents\n- search_query: str - vector search based on the creators profile description.\n- sorted_by: [agent_rating, agent_runs] -",
|
||||
"operationId": "getV2List store creators",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -2871,7 +2871,7 @@
|
||||
"get": {
|
||||
"tags": ["v2", "store", "public"],
|
||||
"summary": "Get creator details",
|
||||
"description": "Get the details of a creator.\nResults are cached for 1 hour.\n- Creator Details Page",
|
||||
"description": "Get the details of a creator.\n- Creator Details Page",
|
||||
"operationId": "getV2Get creator details",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -2905,7 +2905,7 @@
|
||||
"get": {
|
||||
"tags": ["v2", "store", "private"],
|
||||
"summary": "Get my agents",
|
||||
"description": "Get user's own agents.\nResults are cached for 5 minutes per user.",
|
||||
"description": "Get user's own agents.",
|
||||
"operationId": "getV2Get my agents",
|
||||
"security": [{ "HTTPBearerJWT": [] }],
|
||||
"parameters": [
|
||||
@@ -3000,7 +3000,7 @@
|
||||
"get": {
|
||||
"tags": ["v2", "store", "private"],
|
||||
"summary": "List my submissions",
|
||||
"description": "Get a paginated list of store submissions for the authenticated user.\nResults are cached for 1 hour per user.\n\nArgs:\n user_id (str): ID of the authenticated user\n page (int, optional): Page number for pagination. Defaults to 1.\n page_size (int, optional): Number of submissions per page. Defaults to 20.\n\nReturns:\n StoreListingsResponse: Paginated list of store submissions\n\nRaises:\n HTTPException: If page or page_size are less than 1",
|
||||
"description": "Get a paginated list of store submissions for the authenticated user.\n\nArgs:\n user_id (str): ID of the authenticated user\n page (int, optional): Page number for pagination. Defaults to 1.\n page_size (int, optional): Number of submissions per page. Defaults to 20.\n\nReturns:\n StoreListingsResponse: Paginated list of store submissions\n\nRaises:\n HTTPException: If page or page_size are less than 1",
|
||||
"operationId": "getV2List my submissions",
|
||||
"security": [{ "HTTPBearerJWT": [] }],
|
||||
"parameters": [
|
||||
|
||||
Reference in New Issue
Block a user