Skip to content

Commit

Permalink
fet(Cache): add a generic cache
Browse files Browse the repository at this point in the history
  • Loading branch information
breakthewall committed Jun 8, 2021
1 parent a75f48a commit 4892c30
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 3 deletions.
3 changes: 3 additions & 0 deletions RELEASE
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
1.13.0 - Jun 8, 2021
- feat(Cache): add a simple and non-persitent cache of generic objects

1.12.0 - May 27, 2021
- feat(print): add print_OK_adv() func
- feat(print): add print_title_adv() func
Expand Down
177 changes: 177 additions & 0 deletions brs_utils/Cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
"""A class to represent a cache of objects."""
# The MIT License (MIT)
#
# Copyright (c) 2018 Institute for Molecular Systems Biology, ETH Zurich.
# Copyright (c) 2019 Novo Nordisk Foundation Center for Biosustainability,
# Technical University of Denmark
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

from typing import (
Dict,
List,
TypeVar
)
from logging import (
Logger,
getLogger,
ERROR
)
from copy import deepcopy


class Cache:

# static and private
__objects = {}
__logger = getLogger(__name__)
__logger.setLevel(ERROR)

@staticmethod
def set_log_level(level: int) -> None:
Cache.__logger.setLevel(level)

@staticmethod
def get_log_level() -> None:
return Cache.__logger.level

@staticmethod
def add(obj: TypeVar, id: str = None) -> None:
'''
Add an object to the cache.
Parameters
----------
object: TypeVar
The object to add
id: str
ID of the object to add
'''
if id is None:
try:
id = obj.get_id()
except AttributeError:
Cache.__logger.error(f'id is not given and obj has no attribute get_id, nothing added')
Cache.__objects[id] = obj

@staticmethod
def remove(obj: TypeVar) -> None:
'''
Del an object from the cache.
Parameters
----------
object: TypeVar
The object to remove
'''
if obj is not None:
try:
# Find object by ID
Cache.remove_object_by_id(
list(Cache.get_objects().keys())[list(Cache.get_objects().values()).index(object)]
)
except ValueError:
Cache.__logger.warning(f'No such object {id} found in cache, nothing deleted.')
else:
Cache.__logger.warning(f'Object passed is None, nothing deleted.')

@staticmethod
def clean(
) -> None:
'''
Remove all objects from the cache.
'''
for obj_id in Cache.get_list_of_objects():
Cache.remove_object_by_id(obj_id)

@staticmethod
def rename(id: str, new_id: str) -> None:
'''
Rename an object of the cache.
Parameters
----------
id: str
ID of the object to add
'''
if id not in Cache.get_list_of_objects():
Cache.__logger.warning(f'Compound {id} already in cache, nothing added.')
else:
Cache.__objects[new_id] = deepcopy(Cache.get(id))
Cache.remove_object_by_id(id)

@staticmethod
def remove_object_by_id(id: str) -> None:
'''
Del an object from the cache by ID.
Parameters
----------
id: str
ID of the object to remove
'''
if id in Cache.get_list_of_objects():
del Cache.__objects[id]
else:
Cache.__logger.warning(f'No such object {id} found in cache, nothing deleted.')

@staticmethod
def get(id: str) -> TypeVar:
'''
Return the object with ID from the cache
Parameters
----------
id: str
ID of the object to return from the cache
Returns
-------
object: TypeVar
The object with the given ID
'''
try:
return Cache.get_objects()[id]
except KeyError:
return None

@staticmethod
def get_objects() -> Dict:
'''
Return a dictionary of all objects in the cache.
Returns
-------
objects: Dict
All objects in the cache
'''
return Cache.__objects

@staticmethod
def get_list_of_objects() -> List[str]:
'''
Return IDs of all objects in the cache.
Returns
-------
object_ids: List
All object IDs in the cache
'''
return list(Cache.__objects.keys())

1 change: 1 addition & 0 deletions brs_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
create_logger,
add_arguments as add_logger_args,
)
from brs_utils.Cache import Cache
from brs_utils._version import (
__version__
)
Expand Down
2 changes: 1 addition & 1 deletion brs_utils/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.12.0"
__version__ = "1.13.0"
5 changes: 3 additions & 2 deletions recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ test:
- {{ name|lower }}
requires:
- pytest
- pytest-cov
commands:
- cd tests
- pytest -v
- python -m {{ name|lower }} --help
- cd tests ; pytest -v --cov --cov-report term-missing

about:
home: https://github.com/brsynth/{{ name|lower }}
Expand Down
Binary file added tests/.coverage
Binary file not shown.
123 changes: 123 additions & 0 deletions tests/test_Cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""
Created on June 17 2020
@author: Joan Hérisson
"""

from unittest import TestCase

from brs_utils import Cache


class Test_Cache(TestCase):

def setUp(self):
self.__d = {'a': 1, 'b': 2}

def test_add_with_id(self):
Cache.clean()
Cache.add(self.__d, 'd')
self.assertDictEqual(
Cache.get('d'),
self.__d
)

def test_add_wo_id(self):
Cache.clean()
Cache.add(self.__d)
self.assertEqual(
Cache.get('d'),
None
)

def test_log_level(self):
Cache.clean()
level = 40
Cache.set_log_level(level)
self.assertEqual(
Cache.get_log_level(),
level
)

def test_remove(self):
Cache.clean()
self.assertEqual(
Cache.remove('d'),
None
)

def test_remove_none(self):
Cache.clean()
self.assertEqual(
Cache.remove(None),
None
)

def test_rename(self):
Cache.clean()
Cache.add(self.__d, 'd')
Cache.rename('d', 'd1')
self.assertDictEqual(
Cache.get('d1'),
self.__d
)
self.assertEqual(
Cache.get('d'),
None
)

def test_rename_wrong_id(self):
Cache.clean()
Cache.add(self.__d, 'd')
Cache.rename('d2', 'd1')
self.assertEqual(
Cache.get('d1'),
None
)
self.assertDictEqual(
Cache.get('d'),
self.__d
)

def test_remove_object_by_id(self):
Cache.clean()
Cache.add(self.__d, 'd')
Cache.remove_object_by_id('d')
self.assertEqual(
Cache.get('d'),
None
)

def test_remove_object_by_wrong_id(self):
Cache.clean()
Cache.add(self.__d, 'd')
Cache.remove_object_by_id('d1')
self.assertDictEqual(
Cache.get('d'),
self.__d
)

def test_get_wrong_id(self):
Cache.clean()
Cache.add(self.__d, 'd')
self.assertEqual(
Cache.get('d1'),
None
)

def test_get_objects(self):
Cache.clean()
Cache.add(self.__d, 'd')
self.assertDictEqual(
Cache.get_objects(),
{'d': self.__d}
)

def test_get_list_of_objects(self):
Cache.clean()
Cache.add(self.__d, 'd')
self.assertListEqual(
Cache.get_list_of_objects(),
['d']
)

0 comments on commit 4892c30

Please sign in to comment.