From 4892c3074d8969a6a985e6664073788dc8c7cf93 Mon Sep 17 00:00:00 2001 From: breakthewall Date: Tue, 8 Jun 2021 09:53:27 +0200 Subject: [PATCH] fet(Cache): add a generic cache --- RELEASE | 3 + brs_utils/Cache.py | 177 ++++++++++++++++++++++++++++++++++++++++++ brs_utils/__init__.py | 1 + brs_utils/_version.py | 2 +- recipe/meta.yaml | 5 +- tests/.coverage | Bin 0 -> 53248 bytes tests/test_Cache.py | 123 +++++++++++++++++++++++++++++ 7 files changed, 308 insertions(+), 3 deletions(-) create mode 100644 brs_utils/Cache.py create mode 100644 tests/.coverage create mode 100644 tests/test_Cache.py diff --git a/RELEASE b/RELEASE index c6d33a3..d777fb4 100644 --- a/RELEASE +++ b/RELEASE @@ -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 diff --git a/brs_utils/Cache.py b/brs_utils/Cache.py new file mode 100644 index 0000000..c461527 --- /dev/null +++ b/brs_utils/Cache.py @@ -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()) + diff --git a/brs_utils/__init__.py b/brs_utils/__init__.py index bcec7d8..3e0b45a 100644 --- a/brs_utils/__init__.py +++ b/brs_utils/__init__.py @@ -41,6 +41,7 @@ create_logger, add_arguments as add_logger_args, ) +from brs_utils.Cache import Cache from brs_utils._version import ( __version__ ) diff --git a/brs_utils/_version.py b/brs_utils/_version.py index b518f6e..9a34ccc 100644 --- a/brs_utils/_version.py +++ b/brs_utils/_version.py @@ -1 +1 @@ -__version__ = "1.12.0" +__version__ = "1.13.0" diff --git a/recipe/meta.yaml b/recipe/meta.yaml index 62e6655..8b1487f 100644 --- a/recipe/meta.yaml +++ b/recipe/meta.yaml @@ -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 }} diff --git a/tests/.coverage b/tests/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..111467e5c5b44b991dbbe4801e6db8f3de644764 GIT binary patch literal 53248 zcmeI4?{6GO8OLXD&$qX>&l51#HF1MCNN}<0^Z6Q*I4(b8LW(LPm89X95M1}}#`cE0 z-OKKtf2ipXUxFgR8w66-e*hu+u5Wmagai^V5TsHSA*zPHpa_8w3POTJi09cKK0C2X zPO8-4eph$1yEF65JoA}nW^Q)o_L=kNoKRUyp5L}Z>zFtoG)Y9@KizhjzWCLf>vEKXwaeE}UJwc-FeO__=dut=P{xI&9HVE?bM1@2y!K z)UE$ zCW?^c7OM3y_Mk+Rv+QyV>u4fAZTV_R`O0mmAlBlj)0`fjzWU;@Q5+xFuB)g_w%?%7 z$d1yGN53dfEm1-Z&u_B!b>DUyD=L_=?5z?uJkoPLj7#f#?MmobEys|^dc&??^ zRihiK=B@>tVQA?=@V1wR(t4H)^lU7d?y9ZIpRxLqGf^>I zZ^q(6kOh2vBv(8&t_iYAwH{n<(GRbh-3`6yzDAW_tCBN%?L&E^I5nkRzZ@9~;N^_{jBSxlg)flyzNMqBE-4Q?s{m!aXUD&JmKvq@;&Exg}`M5a?UbH%e$ zdzsFb3Dl}vm&2O}b4GD;QtO$K86~xqoQxeNl-A@f-Dn!rR(8}U`-%F-# zBnhG7l#N_*adNNXB=Kt1Cbiu1*h7!n{g}Wy4aTD+KRd_xp)xz zydA30X)BAPM9M_=v}OlZ!&ekYL+Er#f4AD_N~%@ac3X-4s}$F$9E(gx%1 zr54aw-nLVT*!0s)A9&i-~a&-009sH z0T2KI5C8!X009sHfe!|O(X4)0rkf(>4(a;DG2RsL_}t^w^4y8?+`Kh+YT%1skjuxf+%hQ!wGJm9Xcf{C9 z`Mts&IXU*~SVulH_O|@xzA$nW0t7$+1V8`;KmY_l00ck)1VBKDC-mpEK_=AO=Ktor z-W^Cbo&V=g>1zWCZq5IVlX@_iWIX@R4XSE7{~sDO!({%S9aJuv|LX(SM}PjGnb+6M z|6P~6%>VKJKXe5k00JNY0w4eaAOHd&00JNY0wA!j3Fx$Gpup?@n!GFMg98LW00ck) z1V8`;KmY_l00ck)1VCUP643R68U6oXo)_}Z@=f_``Ko+ben+m!uag%J5C8!X009sH z0T2KI5C8!X009sHfxQUi%&caPnVCoPbY943gt+$o-@hsD=5D_6n)Xxe4RNdR8=;LF z6ne<$9g&pIX_=!tE7yDEOJ{x{J|)C~4-b*YCx&`Qv@7C3{@N{V{b%~evgG$uqPT&fOeDu~;W3ix9oJaMZ)L6aKBN@7VB-6`_Y`*#tEo+)Hy&EsQ z@SiK6dGVdU-Tl|?+qd=T|NrvmLf)bG|Nm9~QNAT_%HPUgkrxgS009sH0T2KI5C8!X z009sH0T2Lzhlc<^JkU%tI_2}x$uOc*E*G7KhN4q88=Z7LI%P6hdL9t1|BC|;Php@5 z2!H?xfB*=900@8p2!H?xfB*=9zMGyc15C8!X009sH0T2KI5C8!X zc$f&p|NnoT_y7M>{$2h>z9ZkJHv!y~uRly$LKhGK0T2KI5C8!X009sH0T2KI5CDOD d2