Skip to content

Commit

Permalink
Merge pull request #1 from GigantPro/AddBaseChacheLogic
Browse files Browse the repository at this point in the history
Add cahce decorator
  • Loading branch information
GigantPro authored Jul 19, 2023
2 parents 1c4c63b + ae57de6 commit 108a29a
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 9 deletions.
2 changes: 2 additions & 0 deletions frozenclass/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@

from .data_controller import DataController
from .auto_freeze import AutoFreeze

from .cache import CacheController
6 changes: 3 additions & 3 deletions frozenclass/auto_freeze.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from typing import Any
from typing import Any, Callable

from .data_controller import DataController
from .exceptions import NoSave


def AutoFreeze(saves_path: str = 'saves', mode: str = 'freeze'):
def AutoFreeze(saves_path: str = 'saves', mode: str = 'freeze') -> Callable:
"""Automatically saves changes to variables made to the class
Args:
saves_path (str, optional): Path to save folder. Defaults to 'saves'.
mode (str, optional): Save mode (as in DataController): 'freeze' or 'deep_freeze'. Defaults to 'freeze'.
"""
def wrapper_func(target_class: Any):
def wrapper_func(target_class: Any) -> Any:
if '__name__' not in target_class.__dict__:
raise AttributeError('Your class must contain a variable called __name__')

Expand Down
43 changes: 43 additions & 0 deletions frozenclass/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Any, Callable
from datetime import time, datetime, timedelta


class CacheController:
"""The main class of the cache logic. Includes all caches logic"""
def cache(*, ttl: time | None = time(minute=10)) -> Callable: # ( TTL_end, result )
"""Function-decorate for runtime caching.
The cache can either be overwritten or remain until the program terminates.
:param ttl: Time-To-Live of cached valume, defaults to time(minute=10)
:type ttl: time | None, optional
:return: Decorated func
:rtype: Callable
"""
def wrapper_func(target_func: Callable) -> Callable:
__cached_vals = {}


def cached_func_with_time(*args, **kwargs) -> Any:
cached_ = __cached_vals.get((*args, *kwargs), None)
if cached_ and cached_[0] > datetime.now():
return cached_[1]

result = target_func(*args, **kwargs)

__cached_vals[(*args, *kwargs)] = \
(datetime.now() + timedelta(hours=ttl.hour, minutes=ttl.minute, seconds=ttl.second), result)

return result


def cached_func_without_time(*args, **kwargs) -> Any:
try:
return __cached_vals[(*args, *kwargs)]
except KeyError:
result = target_func(*args, **kwargs)
__cached_vals[(*args, *kwargs)] = result
return result


return cached_func_with_time if ttl else cached_func_without_time
return wrapper_func
9 changes: 6 additions & 3 deletions frozenclass/data_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ def load_save(self, save_name: str) -> Any:
for save_filename in list_dir:
parser = DataParser(f"{self._saves_path}/{save_filename}", self)
parsed_content = parser.parse_file_content()
if parsed_content["SavedModel"]["save_name"].lower() == save_name.lower():
if parsed_content and \
parsed_content["SavedModel"]["save_name"].lower() == save_name.lower():
return parser.parse_file()
raise NoSave("save_name")

Expand All @@ -79,7 +80,8 @@ def load_saved_vars(self, save_name: str) -> Any:
for save_filename in list_dir:
parser = DataParser(f"{self._saves_path}/{save_filename}", self)
parsed_content = parser.parse_file_content()
if parsed_content["SavedModel"]["save_name"].lower() == save_name.lower():
if parsed_content and \
parsed_content["SavedModel"]["save_name"].lower() == save_name.lower():
return parser.parse_saved_args()
raise NoSave("save_name")

Expand All @@ -97,7 +99,8 @@ def dalete_save(self, save_name: str) -> None:
for save_filename in list_dir:
parser = DataParser(f"{self._saves_path}/{save_filename}", self)
parsed_content = parser.parse_file_content()
if parsed_content["SavedModel"]["save_name"] == save_name:
if parsed_content and \
parsed_content["SavedModel"]["save_name"] == save_name:
platform_ = platform.system()
if platform_ == 'Linux':
os.system(f'rm -rf "{self._saves_path}/{save_filename}"')
Expand Down
7 changes: 5 additions & 2 deletions frozenclass/dataparser/data_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,13 @@ def _get_vars_from_saved_data(self, saved_data: dict) -> dict[str: Any]:
res[var_desc['var_name']] = get_value_by_type(var_desc['var_value'], var_desc['var_type'])
return res

def parse_file_content(self, file_name: str | None = None) -> dict[Any]:
def parse_file_content(self, file_name: str | None = None) -> dict[Any] | None:
file_name = file_name if file_name else self.filename
with open(file_name, "r", encoding="utf-8") as file:
file_content = file.readlines()
try:
file_content = file.readlines()
except UnicodeDecodeError:
return None
file_content = [x.strip() for x in file_content if x.strip() != ""]

saved_data = {}
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ disable = [
"E0401",
"W1401",
"W0212",
"C0103"
"C0103",
"E0211"
]
72 changes: 72 additions & 0 deletions tests/test_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import sqlite3
from datetime import time
from time import sleep
from timeit import timeit

from frozenclass import CacheController


def test_cache_with_time():
b = 1

@CacheController.cache(ttl=time(second=1))
def test_cache():
return b

assert test_cache() == 1

b = 2
assert test_cache() == 1

sleep(1)
assert test_cache() == 2


def test_cache_without_time():
b = 1

@CacheController.cache(ttl=None)
def test_cache():
return b

assert test_cache() == 1

b = 2
assert test_cache() == 1

sleep(1)
assert test_cache() == 1

def test_speed():
base = sqlite3.connect('test_saves/test.db')
cur = base.cursor()

base.execute('CREATE TABLE IF NOT EXISTS test (number PRIMARY KEY, value)')
try:
[cur.execute('INSERT INTO test VALUES (?, ?)', (i, i ** 2)) for i in range(1000)]
except: # pylint: disable=bare-except
pass
base.commit()

@CacheController.cache(ttl=time(second=1))
def test_cache(cur):
return cur.execute('SELECT * FROM test').fetchall()

def test_cache_without_lib(cur): # pylint: disable=possibly-unused-variable
return cur.execute('SELECT * FROM test').fetchall()

assert (timeit('test_cache(cur)', globals=locals(), number=1000) / 1000) < .00001
assert(timeit('test_cache(cur)', globals=locals(), number=1000) / 1000) < \
(timeit('test_cache_without_lib(cur)', globals=locals(), number=1000) / 1000)


@CacheController.cache(ttl=None)
def test_cache(cur): # pylint: disable=function-redefined
return cur.execute('SELECT * FROM test').fetchall()

def test_cache_without_lib(cur): # pylint: disable=function-redefined
return cur.execute('SELECT * FROM test').fetchall()

assert (timeit('test_cache(cur)', globals=locals(), number=1000) / 1000) < .00001
assert(timeit('test_cache(cur)', globals=locals(), number=1000) / 1000) < \
(timeit('test_cache_without_lib(cur)', globals=locals(), number=1000) / 1000)

0 comments on commit 108a29a

Please sign in to comment.