Merge pull request 'v4.2.0' (#207) from dev into main
All checks were successful
Analysis / SonarCloud (push) Successful in 51s
Tests / Build and Test (3.11) (push) Successful in 1m11s
Tests / Build and Test (3.12) (push) Successful in 1m7s
Tests / Build and Test (3.13) (push) Successful in 1m6s
Upload Python Package / release-build (release) Successful in 21s
Upload Python Package / gitea-publish (release) Successful in 22s
Upload Python Package / pypi-publish (release) Successful in 13s

Reviewed-on: #207
This commit is contained in:
2025-05-18 18:38:49 +03:00
8 changed files with 53 additions and 23 deletions

View File

@@ -16,6 +16,7 @@ There are different sub-packages available:
* pyrogram - Telegram bots with Pyrogram's fork "Pyrofork" * pyrogram - Telegram bots with Pyrogram's fork "Pyrofork"
* pycord - Discord bots with Pycord * pycord - Discord bots with Pycord
* speed - Performance improvements * speed - Performance improvements
* cache - Support for Redis and Memcached
* dev - Dependencies for package development purposes * dev - Dependencies for package development purposes
You can freely choose any sub-package you want, as well as add multiple (comma-separated) or none at all. You can freely choose any sub-package you want, as well as add multiple (comma-separated) or none at all.

View File

@@ -1,2 +1,2 @@
aiofiles>=23.0.0 aiofiles>=23.0.0
typing-extensions~=4.12.2 typing-extensions~=4.13.0

View File

@@ -1,2 +1,2 @@
pymemcache~=4.0.0 pymemcache~=4.0.0
redis~=5.2.1 redis~=6.1.0

View File

@@ -2,11 +2,11 @@ black==25.1.0
build==1.2.2.post1 build==1.2.2.post1
isort==5.13.2 isort==5.13.2
mypy==1.15.0 mypy==1.15.0
pylint==3.3.4 pylint==3.3.7
pytest-asyncio==0.25.3 pytest-asyncio==0.26.0
pytest-cov==6.0.0 pytest-cov==6.1.1
pytest==8.3.4 pytest==8.3.5
tox==4.24.0 tox==4.26.0
twine==6.1.0 twine==6.1.0
types-aiofiles==24.1.0.20241221 types-aiofiles==24.1.0.20250516
types-ujson==5.10.0.20240515 types-ujson==5.10.0.20250326

View File

@@ -1,4 +1,4 @@
__version__ = "4.1.0" __version__ = "4.2.0"
__license__ = "GPL3" __license__ = "GPL3"
__author__ = "Profitroll" __author__ = "Profitroll"

View File

@@ -1,6 +1,6 @@
import logging import logging
from logging import Logger from logging import Logger
from typing import Dict, Any from typing import Dict, Any, Optional
from pymemcache import Client from pymemcache import Client
@@ -13,21 +13,27 @@ logger: Logger = logging.getLogger(__name__)
class CacheMemcached(Cache): class CacheMemcached(Cache):
client: Client client: Client
def __init__(self, client: Client): def __init__(self, client: Client, prefix: Optional[str] = None):
self.client = client self.client: Client = client
self.prefix: str | None = prefix
logger.info("Initialized Memcached for caching") logger.info("Initialized Memcached for caching")
@classmethod @classmethod
def from_config(cls, engine_config: Dict[str, Any]) -> "CacheMemcached": def from_config(cls, engine_config: Dict[str, Any], prefix: Optional[str] = None) -> "CacheMemcached":
if "uri" not in engine_config: if "uri" not in engine_config:
raise KeyError( raise KeyError(
"Cache configuration is invalid. Please check if all keys are set (engine: memcached)" "Cache configuration is invalid. Please check if all keys are set (engine: memcached)"
) )
return cls(Client(engine_config["uri"], default_noreply=True)) return cls(Client(engine_config["uri"], default_noreply=True), prefix=prefix)
def _get_prefixed_key(self, key: str) -> str:
return key if self.prefix is None else f"{self.prefix}_{key}"
def get_json(self, key: str) -> Any | None: def get_json(self, key: str) -> Any | None:
key = self._get_prefixed_key(key)
try: try:
result: Any | None = self.client.get(key, None) result: Any | None = self.client.get(key, None)
@@ -43,6 +49,8 @@ class CacheMemcached(Cache):
return None if result is None else _string_to_json(result) return None if result is None else _string_to_json(result)
def get_string(self, key: str) -> str | None: def get_string(self, key: str) -> str | None:
key = self._get_prefixed_key(key)
try: try:
result: str | None = self.client.get(key, None) result: str | None = self.client.get(key, None)
@@ -62,6 +70,8 @@ class CacheMemcached(Cache):
raise NotImplementedError() raise NotImplementedError()
def set_json(self, key: str, value: Any) -> None: def set_json(self, key: str, value: Any) -> None:
key = self._get_prefixed_key(key)
try: try:
self.client.set(key, _json_to_string(value)) self.client.set(key, _json_to_string(value))
logger.debug("Set json cache key '%s'", key) logger.debug("Set json cache key '%s'", key)
@@ -70,6 +80,8 @@ class CacheMemcached(Cache):
return None return None
def set_string(self, key: str, value: str) -> None: def set_string(self, key: str, value: str) -> None:
key = self._get_prefixed_key(key)
try: try:
self.client.set(key, value) self.client.set(key, value)
logger.debug("Set string cache key '%s'", key) logger.debug("Set string cache key '%s'", key)
@@ -82,6 +94,8 @@ class CacheMemcached(Cache):
raise NotImplementedError() raise NotImplementedError()
def delete(self, key: str) -> None: def delete(self, key: str) -> None:
key = self._get_prefixed_key(key)
try: try:
self.client.delete(key) self.client.delete(key)
logger.debug("Deleted cache key '%s'", key) logger.debug("Deleted cache key '%s'", key)

View File

@@ -1,6 +1,6 @@
import logging import logging
from logging import Logger from logging import Logger
from typing import Dict, Any from typing import Dict, Any, Optional
from redis import Redis from redis import Redis
@@ -13,21 +13,27 @@ logger: Logger = logging.getLogger(__name__)
class CacheRedis(Cache): class CacheRedis(Cache):
client: Redis client: Redis
def __init__(self, client: Redis): def __init__(self, client: Redis, prefix: Optional[str] = None):
self.client = client self.client: Redis = client
self.prefix: str | None = prefix
logger.info("Initialized Redis for caching") logger.info("Initialized Redis for caching")
@classmethod @classmethod
def from_config(cls, engine_config: Dict[str, Any]) -> Any: def from_config(cls, engine_config: Dict[str, Any], prefix: Optional[str] = None) -> Any:
if "uri" not in engine_config: if "uri" not in engine_config:
raise KeyError( raise KeyError(
"Cache configuration is invalid. Please check if all keys are set (engine: memcached)" "Cache configuration is invalid. Please check if all keys are set (engine: memcached)"
) )
return cls(Redis.from_url(engine_config["uri"])) return cls(Redis.from_url(engine_config["uri"]), prefix=prefix)
def _get_prefixed_key(self, key: str) -> str:
return key if self.prefix is None else f"{self.prefix}_{key}"
def get_json(self, key: str) -> Any | None: def get_json(self, key: str) -> Any | None:
key = self._get_prefixed_key(key)
try: try:
result: Any | None = self.client.get(key) result: Any | None = self.client.get(key)
@@ -43,6 +49,8 @@ class CacheRedis(Cache):
return None if result is None else _string_to_json(result) return None if result is None else _string_to_json(result)
def get_string(self, key: str) -> str | None: def get_string(self, key: str) -> str | None:
key = self._get_prefixed_key(key)
try: try:
result: str | None = self.client.get(key) result: str | None = self.client.get(key)
@@ -62,6 +70,8 @@ class CacheRedis(Cache):
raise NotImplementedError() raise NotImplementedError()
def set_json(self, key: str, value: Any) -> None: def set_json(self, key: str, value: Any) -> None:
key = self._get_prefixed_key(key)
try: try:
self.client.set(key, _json_to_string(value)) self.client.set(key, _json_to_string(value))
logger.debug("Set json cache key '%s'", key) logger.debug("Set json cache key '%s'", key)
@@ -70,6 +80,8 @@ class CacheRedis(Cache):
return None return None
def set_string(self, key: str, value: str) -> None: def set_string(self, key: str, value: str) -> None:
key = self._get_prefixed_key(key)
try: try:
self.client.set(key, value) self.client.set(key, value)
logger.debug("Set string cache key '%s'", key) logger.debug("Set string cache key '%s'", key)
@@ -82,6 +94,8 @@ class CacheRedis(Cache):
raise NotImplementedError() raise NotImplementedError()
def delete(self, key: str) -> None: def delete(self, key: str) -> None:
key = self._get_prefixed_key(key)
try: try:
self.client.delete(key) self.client.delete(key)
logger.debug("Deleted cache key '%s'", key) logger.debug("Deleted cache key '%s'", key)

View File

@@ -1,4 +1,4 @@
from typing import Dict, Any, Literal from typing import Dict, Any, Literal, Optional
from ..classes import CacheMemcached, CacheRedis from ..classes import CacheMemcached, CacheRedis
@@ -6,6 +6,7 @@ from ..classes import CacheMemcached, CacheRedis
def create_cache_client( def create_cache_client(
config: Dict[str, Any], config: Dict[str, Any],
engine: Literal["memcached", "redis"] | None = None, engine: Literal["memcached", "redis"] | None = None,
prefix: Optional[str] = None,
) -> CacheMemcached | CacheRedis: ) -> CacheMemcached | CacheRedis:
if engine not in ["memcached", "redis"] or engine is None: if engine not in ["memcached", "redis"] or engine is None:
raise KeyError(f"Incorrect cache engine provided. Expected 'memcached' or 'redis', got '{engine}'") raise KeyError(f"Incorrect cache engine provided. Expected 'memcached' or 'redis', got '{engine}'")
@@ -17,8 +18,8 @@ def create_cache_client(
match engine: match engine:
case "memcached": case "memcached":
return CacheMemcached.from_config(config["cache"][engine]) return CacheMemcached.from_config(config["cache"][engine], prefix=prefix)
case "redis": case "redis":
return CacheRedis.from_config(config["cache"][engine]) return CacheRedis.from_config(config["cache"][engine], prefix=prefix)
case _: case _:
raise KeyError(f"Cache implementation for the engine '{engine}' is not present.") raise KeyError(f"Cache implementation for the engine '{engine}' is not present.")