cli/srecli/service/config.py (186 lines of code) (raw):
from abc import ABC, abstractmethod
from concurrent.futures import ThreadPoolExecutor, as_completed
from itertools import chain
from pathlib import Path
from typing import Generator, TYPE_CHECKING
from botocore.credentials import JSONFileCache
from srecli.service.constants import CONFIG_FOLDER, CONF_ACCESS_TOKEN, \
CONF_API_LINK, CONF_ITEMS_PER_COLUMN, CONF_REFRESH_TOKEN
from srecli.service.logger import get_logger
if TYPE_CHECKING:
from modular_cli_sdk.services.credentials_manager import (
AbstractCredentialsManager)
SYSTEM_LOG = get_logger(__name__)
Json = dict | list | float | int | str | bool
class AbstractCustodianConfig(ABC):
@property
@abstractmethod
def api_link(self) -> str | None:
...
@api_link.setter
@abstractmethod
def api_link(self, value):
...
@property
@abstractmethod
def access_token(self) -> str | None:
...
@access_token.setter
@abstractmethod
def access_token(self, value):
...
@property
@abstractmethod
def refresh_token(self) -> str | None:
...
@refresh_token.setter
@abstractmethod
def refresh_token(self, value):
...
@property
@abstractmethod
def items_per_column(self) -> int | None:
...
@items_per_column.setter
@abstractmethod
def items_per_column(self, value: int):
...
@abstractmethod
def items(self) -> Generator[tuple[str, Json], None, None]:
...
@abstractmethod
def clear(self):
...
@abstractmethod
def set(self, key: str, value: Json):
...
@abstractmethod
def update(self, dct: dict):
...
class CustodianCLIConfig(JSONFileCache, AbstractCustodianConfig):
"""
The inner implementation can be rewritten to YAML. Or i you want, you
can same all the data in one file. The important thin is, data must be
read from file each time the property is invoked.
"""
CACHE_DIR = Path.home() / CONFIG_FOLDER
def __init__(self, prefix: str = 'root', working_dir: Path = CACHE_DIR):
super().__init__(working_dir=working_dir / prefix)
def get(self, cache_key: str) -> Json | None:
if cache_key in self:
return self[cache_key]
def set(self, key: str, value: Json):
self[key] = value
def update(self, dct: dict):
for key, value in dct.items():
self.set(key, value)
@property
def items_per_column(self) -> int | None:
"""
If None, all the items is shown. It's the default behaviour
:return:
"""
return self.get(CONF_ITEMS_PER_COLUMN)
@items_per_column.setter
def items_per_column(self, value: int):
assert isinstance(value, (int, type(None)))
self[CONF_ITEMS_PER_COLUMN] = value
@items_per_column.deleter
def items_per_column(self):
if CONF_ITEMS_PER_COLUMN in self:
del self[CONF_ITEMS_PER_COLUMN]
@property
def api_link(self) -> str | None:
return self.get(CONF_API_LINK)
@api_link.setter
def api_link(self, value: str):
self[CONF_API_LINK] = value
@api_link.deleter
def api_link(self):
if CONF_API_LINK in self:
del self[CONF_API_LINK]
@property
def access_token(self) -> str | None:
return self.get(CONF_ACCESS_TOKEN)
@access_token.setter
def access_token(self, value: str):
self[CONF_ACCESS_TOKEN] = value
@access_token.deleter
def access_token(self):
if CONF_ACCESS_TOKEN in self:
del self[CONF_ACCESS_TOKEN]
@property
def refresh_token(self) -> str | None:
return self.get(CONF_REFRESH_TOKEN)
@refresh_token.setter
def refresh_token(self, value: str):
self[CONF_REFRESH_TOKEN] = value
@classmethod
def public_config_params(cls) -> list[property]:
return [
cls.api_link,
cls.items_per_column
]
@classmethod
def private_config_params(cls) -> list[property]:
return [
cls.access_token,
cls.refresh_token
]
def items(self) -> Generator[tuple[str, Json], None, None]:
with ThreadPoolExecutor() as executor: # it reads a lot of files
futures = {
executor.submit(prop.fget, self): prop.fget.__name__
for prop in self.public_config_params()
}
for future in as_completed(futures):
yield futures[future], future.result()
def clear(self):
it = chain(self.public_config_params(), self.private_config_params())
with ThreadPoolExecutor() as executor:
for prop in it:
executor.submit(prop.fdel, self)
class CustodianWithCliSDKConfig(AbstractCustodianConfig):
"""
For integration with modular cli sdk
"""
def __init__(self, credentials_manager: 'AbstractCredentialsManager'):
self._credentials_manager = credentials_manager
self._config_dict = {}
@property
def config_dict(self) -> dict:
from modular_cli_sdk.commons.exception import \
ModularCliSdkBaseException
# in order to be able to use other classes from this module
# without cli_sdk installed
if not self._config_dict:
try:
SYSTEM_LOG.info('Getting creds from credentials manager')
self._config_dict = self._credentials_manager.extract()
except ModularCliSdkBaseException:
pass
return self._config_dict
def set(self, key: str, value: Json):
config_dict = self.config_dict
config_dict[key] = value
self._credentials_manager.store(config_dict)
def update(self, dct: dict):
config_dict = self.config_dict
config_dict.update(dct)
self._credentials_manager.store(config_dict)
@property
def api_link(self) -> str | None:
return self.config_dict.get(CONF_API_LINK)
@api_link.setter
def api_link(self, value: str):
self.set(CONF_API_LINK, value)
@property
def access_token(self) -> str | None:
return self.config_dict.get(CONF_ACCESS_TOKEN)
@access_token.setter
def access_token(self, value: str):
self.set(CONF_ACCESS_TOKEN, value)
@property
def refresh_token(self) -> str | None:
return self.config_dict.get(CONF_REFRESH_TOKEN)
@refresh_token.setter
def refresh_token(self, value: str):
self.set(CONF_REFRESH_TOKEN, value)
@property
def items_per_column(self) -> int | None:
return self.config_dict.get(CONF_ITEMS_PER_COLUMN)
@items_per_column.setter
def items_per_column(self, value: int):
assert isinstance(value, (int, type(None)))
self.set(CONF_ITEMS_PER_COLUMN, value)
def items(self) -> Generator[tuple[str, Json], None, None]:
yield CONF_API_LINK, self.api_link
yield CONF_ITEMS_PER_COLUMN, self.items_per_column
def clear(self):
self._credentials_manager.clean_up()