Skip to content
Closed
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
4 changes: 2 additions & 2 deletions app/api/api_v1/genres/list_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ async def get_genres(
],
)
async def search_genres_by_name(
genre_name: str,
search_query: str,
genre_cache_service: GenreCacheServiceDep,
size: PaginationSizeDep = 10,
page: PaginationPageDep = 1,
) -> GenreResponseList:
return await genre_cache_service.search_genres_by_name(genre_name, size, page)
return await genre_cache_service.search_genres_by_name(search_query, size, page)


@router.post(
Expand Down
82 changes: 54 additions & 28 deletions app/cache_services/genre.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import asyncio
from typing import cast

from core.redis.cache_key_service import CacheKeyService
from core.redis.service import RedisService
from schemas.genre import (
GenreCreate,
Expand All @@ -15,68 +17,74 @@ class GenreCacheService:
def __init__(
self,
genre_service: GenreService,
cache_service: RedisService,
redis_service: RedisService,
cache_key_service: CacheKeyService,
) -> None:
self.genre_service = genre_service
self.cache_service = cache_service
self.redis_service = redis_service
self.cache_key_service = cache_key_service

async def get_genre_by_id(self, genre_id: int) -> GenreResponse:
key = RedisService.create_cache_key("genre", genre_id=genre_id)
cached_genre_response = await self.cache_service.get(key, GenreResponse)
key = self.cache_key_service.build_item_key(
entity="genre",
entity_id=genre_id,
action="get",
)
cached_genre_response = await self.redis_service.get(key, GenreResponse)
if cached_genre_response is not None:
return cast(GenreResponse, cached_genre_response)

genre_response = await self.genre_service.get_genre_by_id(genre_id)
await self.cache_service.set(key, genre_response, ttl=1800)
await self.redis_service.set(key, genre_response, ttl=24 * 60 * 60)
return genre_response

async def get_all_genres(
self,
size: int = 10,
page: int = 1,
) -> GenreResponseList:
key = RedisService.create_cache_key(
"genres",
key = await self.cache_key_service.build_list_key(
entity="genre",
action="get",
size=size,
page=page,
)
cached_genres_response = await self.cache_service.get(key, GenreResponseList)
cached_genres_response = await self.redis_service.get(key, GenreResponseList)
if cached_genres_response is not None:
return cast(GenreResponseList, cached_genres_response)

genres_response = await self.genre_service.get_all_genres(size, page)
await self.cache_service.set(key, genres_response, ttl=180)
await self.redis_service.set(key, genres_response, ttl=24 * 60 * 60)
return genres_response

async def search_genres_by_name(
self,
name: str,
search_query: str,
size: int = 10,
page: int = 1,
) -> GenreResponseList:
key = RedisService.create_cache_key(
"genres",
name=name,
key = await self.cache_key_service.build_list_key(
entity="genre",
action="get",
search_query=search_query,
size=size,
page=page,
)
cached_genres_response = await self.cache_service.get(key, GenreResponseList)
cached_genres_response = await self.redis_service.get(key, GenreResponseList)
if cached_genres_response is not None:
return cast(GenreResponseList, cached_genres_response)

genres_response = await self.genre_service.search_genres_by_name(
name,
search_query,
size,
page,
)
await self.cache_service.set(key, genres_response, ttl=180)
await self.redis_service.set(key, genres_response, ttl=24 * 60 * 60)
return genres_response

async def create_genre(self, create_data: GenreCreate) -> GenreResponse:
genre_response = await self.genre_service.create_genre(create_data)
key = RedisService.create_cache_key("genre")
pattern = key + "*"
await self.cache_service.delete_by_pattern(pattern)
await self.cache_key_service.invalidate_list_keys(entity="genre")
return genre_response

async def update_genre(
Expand All @@ -85,9 +93,15 @@ async def update_genre(
update_data: GenreUpdate,
) -> GenreResponse:
genre_response = await self.genre_service.update_genre(genre_id, update_data)
key = RedisService.create_cache_key("genre")
pattern = key + "*"
await self.cache_service.delete_by_pattern(pattern)
key = self.cache_key_service.build_item_key(
entity="genre",
entity_id=genre_response.id,
action="get",
)
await asyncio.gather(
self.cache_key_service.invalidate_list_keys(entity="genre"),
self.redis_service.delete(key=key),
)
return genre_response

async def partial_update_genre(
Expand All @@ -99,13 +113,25 @@ async def partial_update_genre(
genre_id,
update_data,
)
key = RedisService.create_cache_key("genre")
pattern = key + "*"
await self.cache_service.delete_by_pattern(pattern)
key = self.cache_key_service.build_item_key(
entity="genre",
entity_id=genre_response.id,
action="get",
)
await asyncio.gather(
self.cache_key_service.invalidate_list_keys(entity="genre"),
self.redis_service.delete(key=key),
)
return genre_response

async def delete_genre_by_id(self, genre_id: int) -> None:
await self.genre_service.delete_genre_by_id(genre_id)
key = RedisService.create_cache_key("genre")
pattern = key + "*"
await self.cache_service.delete_by_pattern(pattern)
key = self.cache_key_service.build_item_key(
entity="genre",
entity_id=genre_id,
action="get",
)
await asyncio.gather(
self.cache_key_service.invalidate_list_keys(entity="genre"),
self.redis_service.delete(key=key),
)
1 change: 1 addition & 0 deletions app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class NotificationServiceConfig(BaseModel):


class Settings(BaseSettings):
service_name: str = "movie-catalog"
base_dir: Path = Path(__file__).parent.parent
database: DataBaseConfig = DataBaseConfig()
redis: RedisConfig = RedisConfig()
Expand Down
71 changes: 71 additions & 0 deletions app/core/redis/cache_key_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import cast

from core.config import settings
from core.constants import PrimitiveType
from core.redis import RedisService


class CacheKeyService:
list_field = "list"

def __init__(self, redis: RedisService) -> None:
self.redis = redis

async def __get_version(self, entity: str) -> int:
key = self.__create_version_entity_key(entity)
version = await self.redis.get(key, is_integer=True)
return cast(int, version)

async def __update_version(self, entity: str) -> None:
key = self.__create_version_entity_key(entity)
await self.redis.incr_by(key, amount=1)

async def __init_version(self, entity: str) -> int:
key = self.__create_version_entity_key(entity)
start_value = 1
await self.redis.set(
key=key,
value=start_value,
)
return start_value

@classmethod
def __create_version_entity_key(cls, entity: str) -> str:
return f"{settings.service_name}:version:{cls.list_field}:{entity}"

async def invalidate_list_keys(
self,
entity: str,
) -> None:
await self.__update_version(
entity=entity,
)

@staticmethod
def build_item_key(
entity: str,
entity_id: int,
action: str = "get",
**params: PrimitiveType,
) -> str:
key = f"{settings.service_name}:{entity}:{entity_id}:{action}"
for parameter_name, value in params.items():
key = f"{key}:{parameter_name}:{value}"

return key

async def build_list_key(
self,
entity: str,
action: str = "get",
**params: PrimitiveType,
) -> str:
version = await self.__get_version(entity)
if version is None:
version = await self.__init_version(entity)
key = f"{settings.service_name}:{entity}:{self.list_field}:version:{version}:{action}" # noqa: E501

for parameter_name, value in params.items():
key = f"{key}:{parameter_name}:{value}"

return key
7 changes: 6 additions & 1 deletion app/core/redis/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ async def get(self, key: str) -> str | None:
async def exists(self, key: str) -> bool:
return cast(bool, await self._redis.exists(key))

async def set(self, key: str, value: str | int, expire: int) -> None:
async def set(
self,
key: str,
value: str | int,
expire: int | None = None,
) -> None:
await self._redis.set(
key,
value,
Expand Down
10 changes: 8 additions & 2 deletions app/core/redis/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,12 @@ async def _get_boolean(self, key: str) -> bool | None:
return boolean[value]
return None

async def set(self, key: str, value: Any, ttl: int = 300) -> None:
async def set(
self,
key: str,
value: Any,
ttl: int | None = None,
) -> None:
encoded_value = self.convert_object_to_string(value)
await self.redis.set(key, encoded_value, ttl)

Expand Down Expand Up @@ -87,7 +92,8 @@ def create_cache_key(cls, prefix: str, **kwargs: Any) -> str:

@staticmethod
def convert_string_to_object(
value: str | int, schema: type[AnyPydanticType],
value: str | int,
schema: type[AnyPydanticType],
) -> AnyPydanticType | PrimitiveType:
if schema is None:
return value
Expand Down
10 changes: 8 additions & 2 deletions app/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from cache_services import GenreCacheService, MovieCacheService
from core.database import session_factory
from core.redis import RedisClient, RedisService
from core.redis.cache_key_service import CacheKeyService
from dependencies.redis_client import (
get_genre_redis_client as get_genre_redis_client_dependency,
)
Expand Down Expand Up @@ -56,9 +57,14 @@ async def get_genre_redis_service() -> AsyncGenerator[RedisService]:
async def get_genre_cache_service() -> AsyncGenerator[GenreCacheService]:
async with (
get_genre_service() as genre_service,
get_genre_redis_service() as cache_service,
get_genre_redis_service() as redis_service,
):
genre_cache_service = GenreCacheService(genre_service, cache_service)
cache_key_service = CacheKeyService(redis_service)
genre_cache_service = GenreCacheService(
genre_service,
redis_service,
cache_key_service,
)
yield genre_cache_service


Expand Down
8 changes: 7 additions & 1 deletion app/dependencies/cache_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
UserCacheService,
)
from cache_services.watch_history import WatchHistoryCacheService
from core.redis.cache_key_service import CacheKeyService
from core.redis.service import RedisService
from dependencies.redis_services import (
get_favorite_movie_redis_service,
Expand Down Expand Up @@ -49,7 +50,12 @@ async def get_genre_cache_service(
],
) -> AsyncGenerator[GenreCacheService]:
try:
genre_cache_service = GenreCacheService(genre_service, genre_redis_service)
cache_key_service = CacheKeyService(genre_redis_service)
genre_cache_service = GenreCacheService(
genre_service,
genre_redis_service,
cache_key_service,
)
yield genre_cache_service
finally:
"""
Expand Down
4 changes: 2 additions & 2 deletions app/services/genre.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ async def get_all_genres(

async def search_genres_by_name(
self,
name: str,
search_query: str,
size: int = 10,
page: int = 1,
) -> GenreResponseList:
genre_list = [
GenreResponse.model_validate(genre)
for genre in await self.genre_repository.search_genres_by_name(
name,
search_query,
size,
page,
)
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/api/api_v1/genres.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
var p = page != null ? page : 1;
var s = size != null ? size : 10;
var q =
"genre_name=" +
"search_query=" +
encodeURIComponent(name) +
"&page=" +
encodeURIComponent(p) +
Expand Down
Loading