Compare commits

..

14 Commits

Author SHA1 Message Date
fdb8db4782 Merge pull request 'v4.1.0' (#189) from dev into main
Some checks failed
Analysis / SonarCloud (push) Successful in 46s
Tests / Build and Test (3.11) (push) Successful in 1m9s
Tests / Build and Test (3.12) (push) Successful in 1m7s
Tests / Build and Test (3.13) (push) Successful in 1m4s
Upload Python Package / release-build (release) Successful in 18s
Upload Python Package / gitea-publish (release) Failing after 20s
Upload Python Package / pypi-publish (release) Failing after 11s
Reviewed-on: #189
2025-02-16 18:37:38 +02:00
508c48d22b Merge pull request 'v4.0.2' (#172) from dev into main
All checks were successful
Analysis / SonarCloud (push) Successful in 46s
Tests / Build and Test (3.11) (push) Successful in 1m16s
Tests / Build and Test (3.12) (push) Successful in 1m24s
Tests / Build and Test (3.13) (push) Successful in 1m24s
Upload Python Package / release-build (release) Successful in 18s
Upload Python Package / gitea-publish (release) Successful in 9s
Upload Python Package / pypi-publish (release) Successful in 15s
Reviewed-on: #172
2025-01-02 15:04:26 +02:00
bfcb067ba5 Merge pull request 'v4.0.1' (#170) from dev into main
All checks were successful
Analysis / SonarCloud (push) Successful in 39s
Tests / Build and Test (3.11) (push) Successful in 1m18s
Tests / Build and Test (3.12) (push) Successful in 1m35s
Tests / Build and Test (3.13) (push) Successful in 1m21s
Reviewed-on: #170
2024-12-29 17:18:48 +02:00
a38b55d270 Merge pull request 'v4.0.0' (#169) from dev into main
Some checks failed
Analysis / SonarCloud (push) Successful in 42s
Tests / Build and Test (3.10) (push) Failing after 57s
Tests / Build and Test (3.11) (push) Successful in 1m4s
Tests / Build and Test (3.12) (push) Successful in 1m13s
Tests / Build and Test (3.9) (push) Failing after 57s
Reviewed-on: #169
2024-12-26 19:59:34 +02:00
9907cc50f1 Merge pull request 'v3.3.1' (#160) from dev into main
All checks were successful
Analysis / SonarCloud (push) Successful in 46s
Tests / Build and Test (3.10) (push) Successful in 1m3s
Tests / Build and Test (3.11) (push) Successful in 1m1s
Tests / Build and Test (3.12) (push) Successful in 1m8s
Tests / Build and Test (3.9) (push) Successful in 1m3s
Reviewed-on: #160
2024-12-16 23:57:08 +02:00
1b60257bc5 Merge pull request 'v3.3.0' (#159) from dev into main
All checks were successful
Analysis / SonarCloud (push) Successful in 43s
Tests / Build and Test (3.10) (push) Successful in 1m2s
Tests / Build and Test (3.11) (push) Successful in 1m2s
Tests / Build and Test (3.12) (push) Successful in 1m8s
Tests / Build and Test (3.9) (push) Successful in 1m4s
Reviewed-on: #159
2024-12-16 23:48:07 +02:00
171e36a491 Merge pull request 'v3.2.3' (#118) from dev into main
All checks were successful
Tests / test (3.10) (push) Successful in 1m2s
Tests / test (3.11) (push) Successful in 58s
Tests / test (3.8) (push) Successful in 1m4s
Tests / test (3.9) (push) Successful in 1m1s
Reviewed-on: #118
2024-07-10 00:07:54 +03:00
c419c684aa Merge pull request 'v3.2.2' (#107) from dev into main
All checks were successful
Tests / test (3.10) (push) Successful in 1m10s
Tests / test (3.11) (push) Successful in 1m4s
Tests / test (3.8) (push) Successful in 1m6s
Tests / test (3.9) (push) Successful in 1m7s
Reviewed-on: #107
2024-05-26 22:44:18 +03:00
748b2b2abb Merge pull request 'v3.2.1' (#106) from dev into main
All checks were successful
Tests / test (3.10) (push) Successful in 1m3s
Tests / test (3.11) (push) Successful in 1m23s
Tests / test (3.8) (push) Successful in 1m7s
Tests / test (3.9) (push) Successful in 1m7s
Reviewed-on: #106
2024-05-26 17:53:00 +03:00
52c2e5cc13 Merge pull request 'v3.2.0' (#105) from dev into main
All checks were successful
Tests / test (3.10) (push) Successful in 1m4s
Tests / test (3.11) (push) Successful in 1m2s
Tests / test (3.8) (push) Successful in 1m29s
Tests / test (3.9) (push) Successful in 1m4s
Reviewed-on: #105
2024-05-26 17:29:44 +03:00
55c61e3fce Merge pull request 'v3.1.0' (#102) from dev into main
All checks were successful
Tests / test (3.10) (push) Successful in 55s
Tests / test (3.11) (push) Successful in 55s
Tests / test (3.8) (push) Successful in 57s
Tests / test (3.9) (push) Successful in 1m27s
Reviewed-on: #102
2024-05-19 16:22:17 +03:00
b9550032ba Merge pull request 'Update to 3.0.1' (#98) from dev into main
All checks were successful
Tests / test (3.10) (push) Successful in 57s
Tests / test (3.11) (push) Successful in 54s
Tests / test (3.8) (push) Successful in 1m8s
Tests / test (3.9) (push) Successful in 55s
Reviewed-on: #98
2024-05-15 00:19:03 +03:00
5ba763246b Merge pull request 'Update to 3.0.0' (#52) from dev into main
All checks were successful
Tests / test (3.10) (push) Successful in 1m15s
Tests / test (3.11) (push) Successful in 1m14s
Tests / test (3.8) (push) Successful in 1m14s
Tests / test (3.9) (push) Successful in 1m22s
Reviewed-on: #52
2024-01-04 00:06:50 +02:00
f0ffdf096d Merge pull request 'Pycord support initial release' (#48) from dev into main
All checks were successful
Tests / test (3.10) (push) Successful in 1m8s
Tests / test (3.11) (push) Successful in 1m5s
Tests / test (3.8) (push) Successful in 1m43s
Tests / test (3.9) (push) Successful in 1m3s
Reviewed-on: #48
2023-12-27 15:00:41 +02:00
10 changed files with 42 additions and 88 deletions

View File

@@ -16,7 +16,6 @@ There are different sub-packages available:
* pyrogram - Telegram bots with Pyrogram's fork "Pyrofork"
* pycord - Discord bots with Pycord
* speed - Performance improvements
* cache - Support for Redis and Memcached
* 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.

View File

@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=77.0.3", "wheel"]
requires = ["setuptools>=62.6", "wheel"]
build-backend = "setuptools.build_meta"
[project]
@@ -9,11 +9,11 @@ authors = [{ name = "Profitroll" }]
description = "Universal bot library with functions needed for basic Discord/Telegram bot development."
readme = "README.md"
requires-python = ">=3.11"
license = "GPL-3.0"
license-files = ["LICENSE"]
license = { text = "GPLv3" }
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
black==25.1.0
build==1.2.2.post1
isort==5.13.2
mypy==1.16.1
pylint==3.3.7
pytest-asyncio==1.0.0
pytest-cov==6.2.1
pytest==8.4.1
tox==4.27.0
mypy==1.15.0
pylint==3.3.4
pytest-asyncio==0.25.3
pytest-cov==6.0.0
pytest==8.3.4
tox==4.24.0
twine==6.1.0
types-aiofiles==24.1.0.20250708
types-ujson==5.10.0.20250326
types-aiofiles==24.1.0.20241221
types-ujson==5.10.0.20240515

View File

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

View File

@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
from typing import Any, Dict
import pymemcache
import redis
@@ -27,16 +27,16 @@ class Cache(ABC):
pass
@abstractmethod
def set_json(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
def set_json(self, key: str, value: Any) -> None:
# TODO This method must also carry out ObjectId conversion!
pass
@abstractmethod
def set_string(self, key: str, value: str, ttl_seconds: Optional[int] = None) -> None:
def set_string(self, key: str, value: str) -> None:
pass
@abstractmethod
def set_object(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
def set_object(self, key: str, value: Any) -> None:
pass
@abstractmethod

View File

@@ -1,6 +1,6 @@
import logging
from logging import Logger
from typing import Dict, Any, Optional
from typing import Dict, Any
from pymemcache import Client
@@ -13,30 +13,21 @@ logger: Logger = logging.getLogger(__name__)
class CacheMemcached(Cache):
client: Client
def __init__(
self, client: Client, prefix: Optional[str] = None, default_ttl_seconds: Optional[int] = None
) -> None:
self.client: Client = client
self.prefix: str | None = prefix
self.default_ttl_seconds: int = default_ttl_seconds if default_ttl_seconds is not None else 0
def __init__(self, client: Client):
self.client = client
logger.info("Initialized Memcached for caching")
@classmethod
def from_config(cls, engine_config: Dict[str, Any], prefix: Optional[str] = None) -> "CacheMemcached":
def from_config(cls, engine_config: Dict[str, Any]) -> "CacheMemcached":
if "uri" not in engine_config:
raise KeyError(
"Cache configuration is invalid. Please check if all keys are set (engine: memcached)"
)
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}"
return cls(Client(engine_config["uri"], default_noreply=True))
def get_json(self, key: str) -> Any | None:
key = self._get_prefixed_key(key)
try:
result: Any | None = self.client.get(key, None)
@@ -52,8 +43,6 @@ class CacheMemcached(Cache):
return None if result is None else _string_to_json(result)
def get_string(self, key: str) -> str | None:
key = self._get_prefixed_key(key)
try:
result: str | None = self.client.get(key, None)
@@ -72,39 +61,27 @@ class CacheMemcached(Cache):
def get_object(self, key: str) -> Any | None:
raise NotImplementedError()
def set_json(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
key = self._get_prefixed_key(key)
def set_json(self, key: str, value: Any) -> None:
try:
self.client.set(
key,
_json_to_string(value),
expire=self.default_ttl_seconds if ttl_seconds is None else ttl_seconds,
)
self.client.set(key, _json_to_string(value))
logger.debug("Set json cache key '%s'", key)
except Exception as exc:
logger.error("Could not set json cache key '%s' due to: %s", key, exc)
return None
def set_string(self, key: str, value: str, ttl_seconds: Optional[int] = None) -> None:
key = self._get_prefixed_key(key)
def set_string(self, key: str, value: str) -> None:
try:
self.client.set(
key, value, expire=self.default_ttl_seconds if ttl_seconds is None else ttl_seconds
)
self.client.set(key, value)
logger.debug("Set string cache key '%s'", key)
except Exception as exc:
logger.error("Could not set string cache key '%s' due to: %s", key, exc)
return None
# TODO Implement binary serialization
def set_object(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
def set_object(self, key: str, value: Any) -> None:
raise NotImplementedError()
def delete(self, key: str) -> None:
key = self._get_prefixed_key(key)
try:
self.client.delete(key)
logger.debug("Deleted cache key '%s'", key)

View File

@@ -1,11 +1,11 @@
import logging
from logging import Logger
from typing import Dict, Any, Optional
from typing import Dict, Any
from redis import Redis
from .cache import Cache
from ..utils._objects import _json_to_string, _string_to_json
from ..utils._objects import _string_to_json, _json_to_string
logger: Logger = logging.getLogger(__name__)
@@ -13,30 +13,21 @@ logger: Logger = logging.getLogger(__name__)
class CacheRedis(Cache):
client: Redis
def __init__(
self, client: Redis, prefix: Optional[str] = None, default_ttl_seconds: Optional[int] = None
) -> None:
self.client: Redis = client
self.prefix: str | None = prefix
self.default_ttl_seconds: int | None = default_ttl_seconds
def __init__(self, client: Redis):
self.client = client
logger.info("Initialized Redis for caching")
@classmethod
def from_config(cls, engine_config: Dict[str, Any], prefix: Optional[str] = None) -> Any:
def from_config(cls, engine_config: Dict[str, Any]) -> Any:
if "uri" not in engine_config:
raise KeyError(
"Cache configuration is invalid. Please check if all keys are set (engine: memcached)"
)
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}"
return cls(Redis.from_url(engine_config["uri"]))
def get_json(self, key: str) -> Any | None:
key = self._get_prefixed_key(key)
try:
result: Any | None = self.client.get(key)
@@ -52,8 +43,6 @@ class CacheRedis(Cache):
return None if result is None else _string_to_json(result)
def get_string(self, key: str) -> str | None:
key = self._get_prefixed_key(key)
try:
result: str | None = self.client.get(key)
@@ -72,37 +61,27 @@ class CacheRedis(Cache):
def get_object(self, key: str) -> Any | None:
raise NotImplementedError()
def set_json(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
key = self._get_prefixed_key(key)
def set_json(self, key: str, value: Any) -> None:
try:
self.client.set(
key,
_json_to_string(value),
ex=self.default_ttl_seconds if ttl_seconds is None else ttl_seconds,
)
self.client.set(key, _json_to_string(value))
logger.debug("Set json cache key '%s'", key)
except Exception as exc:
logger.error("Could not set json cache key '%s' due to: %s", key, exc)
return None
def set_string(self, key: str, value: str, ttl_seconds: Optional[int] = None) -> None:
key = self._get_prefixed_key(key)
def set_string(self, key: str, value: str) -> None:
try:
self.client.set(key, value, ex=self.default_ttl_seconds if ttl_seconds is None else ttl_seconds)
self.client.set(key, value)
logger.debug("Set string cache key '%s'", key)
except Exception as exc:
logger.error("Could not set string cache key '%s' due to: %s", key, exc)
return None
# TODO Implement binary serialization
def set_object(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
def set_object(self, key: str, value: Any) -> None:
raise NotImplementedError()
def delete(self, key: str) -> None:
key = self._get_prefixed_key(key)
try:
self.client.delete(key)
logger.debug("Deleted cache key '%s'", key)

View File

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