refactor(config): abstract config storage and enable dependency injection
Abstract the configuration storage mechanism to allow using custom configurations, such as in-memory setups, when using the tool as a library. This aligns the configuration architecture with the session cache abstraction. - Define ConfigInterface protocol and InMemoryConfig class - Make CacheFile, InMemoryCache, ConfigFile, and InMemoryConfig explicitly inherit their interfaces - Update MdrsService and MdrsClient to accept customizable config_class and config instances - Add validation to check remote parameter consistency in create_connection - Remove unused imports across command files
This commit is contained in:
+2
-2
@@ -66,7 +66,7 @@ class CacheInterface(Protocol):
|
|||||||
def laboratories(self, laboratories: Laboratories) -> None: ...
|
def laboratories(self, laboratories: Laboratories) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
class InMemoryCache:
|
class InMemoryCache(CacheInterface):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__data = CacheData()
|
self.__data = CacheData()
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class InMemoryCache:
|
|||||||
self.__data.laboratories = laboratories
|
self.__data.laboratories = laboratories
|
||||||
|
|
||||||
|
|
||||||
class CacheFile:
|
class CacheFile(CacheInterface):
|
||||||
__serial: int
|
__serial: int
|
||||||
__cache_dir: str
|
__cache_dir: str
|
||||||
__cache_file: str
|
__cache_file: str
|
||||||
|
|||||||
+11
-16
@@ -4,6 +4,7 @@ from unicodedata import normalize
|
|||||||
|
|
||||||
from mdrsclient.api import DoiApi, FilesApi, FoldersApi, LaboratoriesApi, UsersApi
|
from mdrsclient.api import DoiApi, FilesApi, FoldersApi, LaboratoriesApi, UsersApi
|
||||||
from mdrsclient.cache import CacheInterface
|
from mdrsclient.cache import CacheInterface
|
||||||
|
from mdrsclient.config import ConfigInterface
|
||||||
from mdrsclient.connection import MDRSConnection
|
from mdrsclient.connection import MDRSConnection
|
||||||
from mdrsclient.exceptions import IllegalArgumentException, MDRSException, UnauthorizedException, UnexpectedException
|
from mdrsclient.exceptions import IllegalArgumentException, MDRSException, UnauthorizedException, UnexpectedException
|
||||||
from mdrsclient.models import File, Folder, Laboratory, Token, User
|
from mdrsclient.models import File, Folder, Laboratory, Token, User
|
||||||
@@ -14,12 +15,14 @@ from mdrsclient.services import MdrsService
|
|||||||
class MdrsClient(MdrsService):
|
class MdrsClient(MdrsService):
|
||||||
"""Service layer client for MDRS."""
|
"""Service layer client for MDRS."""
|
||||||
|
|
||||||
def __init__(self, connection: MDRSConnection):
|
def __init__(self, connection: MDRSConnection, config_class: type[ConfigInterface] | None = None):
|
||||||
super().__init__(connection)
|
super().__init__(connection, config_class)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_remote(cls, remote: str, cache: CacheInterface | None = None) -> "MdrsClient":
|
def from_remote(
|
||||||
return cls(cls.create_connection(remote, cache))
|
cls, remote: str, cache: CacheInterface | None = None, config: ConfigInterface | None = None
|
||||||
|
) -> "MdrsClient":
|
||||||
|
return cls(cls.create_connection(remote, cache, config))
|
||||||
|
|
||||||
def mkdir(self, remote_path: str) -> None:
|
def mkdir(self, remote_path: str) -> None:
|
||||||
remote, laboratory_name, r_path = self.parse_remote_host_with_path(remote_path)
|
remote, laboratory_name, r_path = self.parse_remote_host_with_path(remote_path)
|
||||||
@@ -208,36 +211,28 @@ class MdrsClient(MdrsService):
|
|||||||
return f"mdrs {__version__}"
|
return f"mdrs {__version__}"
|
||||||
|
|
||||||
def config_create(self, remote: str, url: str) -> None:
|
def config_create(self, remote: str, url: str) -> None:
|
||||||
from mdrsclient.config import ConfigFile
|
|
||||||
|
|
||||||
remote = self.parse_remote_host(remote)
|
remote = self.parse_remote_host(remote)
|
||||||
config = ConfigFile(remote)
|
config = self.config_class(remote)
|
||||||
if config.url is not None:
|
if config.url is not None:
|
||||||
raise IllegalArgumentException(f"Remote host `{remote}` is already exists.")
|
raise IllegalArgumentException(f"Remote host `{remote}` is already exists.")
|
||||||
else:
|
else:
|
||||||
config.url = url
|
config.url = url
|
||||||
|
|
||||||
def config_update(self, remote: str, url: str) -> None:
|
def config_update(self, remote: str, url: str) -> None:
|
||||||
from mdrsclient.config import ConfigFile
|
|
||||||
|
|
||||||
remote = self.parse_remote_host(remote)
|
remote = self.parse_remote_host(remote)
|
||||||
config = ConfigFile(remote)
|
config = self.config_class(remote)
|
||||||
if config.url is None:
|
if config.url is None:
|
||||||
raise IllegalArgumentException(f"Remote host `{remote}` is not exists.")
|
raise IllegalArgumentException(f"Remote host `{remote}` is not exists.")
|
||||||
else:
|
else:
|
||||||
config.url = url
|
config.url = url
|
||||||
|
|
||||||
def config_list(self) -> list:
|
def config_list(self) -> list:
|
||||||
from mdrsclient.config import ConfigFile
|
config = self.config_class("")
|
||||||
|
|
||||||
config = ConfigFile("")
|
|
||||||
return config.list()
|
return config.list()
|
||||||
|
|
||||||
def config_delete(self, remote: str) -> None:
|
def config_delete(self, remote: str) -> None:
|
||||||
from mdrsclient.config import ConfigFile
|
|
||||||
|
|
||||||
remote = self.parse_remote_host(remote)
|
remote = self.parse_remote_host(remote)
|
||||||
config = ConfigFile(remote)
|
config = self.config_class(remote)
|
||||||
if config.url is None:
|
if config.url is None:
|
||||||
raise IllegalArgumentException(f"Remote host `{remote}` is not exists.")
|
raise IllegalArgumentException(f"Remote host `{remote}` is not exists.")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ import getpass
|
|||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from mdrsclient.api import UsersApi
|
|
||||||
from mdrsclient.commands.base import BaseCommand
|
from mdrsclient.commands.base import BaseCommand
|
||||||
from mdrsclient.config import ConfigFile
|
|
||||||
from mdrsclient.connection import MDRSConnection
|
|
||||||
from mdrsclient.exceptions import MissingConfigurationException
|
|
||||||
|
|
||||||
|
|
||||||
class LoginCommand(BaseCommand):
|
class LoginCommand(BaseCommand):
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ from argparse import Namespace
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from mdrsclient.commands.base import BaseCommand
|
from mdrsclient.commands.base import BaseCommand
|
||||||
from mdrsclient.config import ConfigFile
|
|
||||||
from mdrsclient.connection import MDRSConnection
|
|
||||||
from mdrsclient.exceptions import MissingConfigurationException
|
|
||||||
|
|
||||||
|
|
||||||
class LogoutCommand(BaseCommand):
|
class LogoutCommand(BaseCommand):
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ from argparse import Namespace
|
|||||||
from typing import Any, Final
|
from typing import Any, Final
|
||||||
|
|
||||||
from mdrsclient.commands.base import BaseCommand
|
from mdrsclient.commands.base import BaseCommand
|
||||||
from mdrsclient.config import ConfigFile
|
|
||||||
from mdrsclient.connection import MDRSConnection
|
|
||||||
from mdrsclient.exceptions import MissingConfigurationException
|
|
||||||
|
|
||||||
|
|
||||||
class WhoamiCommand(BaseCommand):
|
class WhoamiCommand(BaseCommand):
|
||||||
|
|||||||
+52
-2
@@ -1,6 +1,7 @@
|
|||||||
import configparser
|
import configparser
|
||||||
import os
|
import os
|
||||||
from typing import Final
|
import threading
|
||||||
|
from typing import Final, Protocol, runtime_checkable
|
||||||
|
|
||||||
import validators
|
import validators
|
||||||
|
|
||||||
@@ -9,7 +10,56 @@ from mdrsclient.settings import CONFIG_DIRNAME
|
|||||||
from mdrsclient.utils import FileLock
|
from mdrsclient.utils import FileLock
|
||||||
|
|
||||||
|
|
||||||
class ConfigFile:
|
@runtime_checkable
|
||||||
|
class ConfigInterface(Protocol):
|
||||||
|
remote: str
|
||||||
|
|
||||||
|
def list(self) -> list[tuple[str, str]]: ...
|
||||||
|
@property
|
||||||
|
def url(self) -> str | None: ...
|
||||||
|
@url.setter
|
||||||
|
def url(self, url: str) -> None: ...
|
||||||
|
@url.deleter
|
||||||
|
def url(self) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
class InMemoryConfig(ConfigInterface):
|
||||||
|
__configs: dict[str, str] = {}
|
||||||
|
__lock: threading.Lock = threading.Lock()
|
||||||
|
remote: str
|
||||||
|
|
||||||
|
def __init__(self, remote: str) -> None:
|
||||||
|
self.remote = remote
|
||||||
|
|
||||||
|
def list(self) -> list[tuple[str, str]]:
|
||||||
|
with self.__lock:
|
||||||
|
return list(self.__configs.items())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> str | None:
|
||||||
|
with self.__lock:
|
||||||
|
return self.__configs.get(self.remote)
|
||||||
|
|
||||||
|
@url.setter
|
||||||
|
def url(self, url: str) -> None:
|
||||||
|
if not validators.url(url):
|
||||||
|
raise IllegalArgumentException("malformed URI sequence")
|
||||||
|
with self.__lock:
|
||||||
|
self.__configs[self.remote] = url
|
||||||
|
|
||||||
|
@url.deleter
|
||||||
|
def url(self) -> None:
|
||||||
|
with self.__lock:
|
||||||
|
if self.remote in self.__configs:
|
||||||
|
del self.__configs[self.remote]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def clear(cls) -> None:
|
||||||
|
with cls.__lock:
|
||||||
|
cls.__configs.clear()
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFile(ConfigInterface):
|
||||||
OPTION_URL: Final[str] = "url"
|
OPTION_URL: Final[str] = "url"
|
||||||
CONFIG_FILENAME: Final[str] = "config.ini"
|
CONFIG_FILENAME: Final[str] = "config.ini"
|
||||||
remote: str
|
remote: str
|
||||||
|
|||||||
+13
-4
@@ -5,7 +5,7 @@ from unicodedata import normalize
|
|||||||
|
|
||||||
from mdrsclient.api import DoiApi, FilesApi, FoldersApi, LaboratoriesApi, UsersApi
|
from mdrsclient.api import DoiApi, FilesApi, FoldersApi, LaboratoriesApi, UsersApi
|
||||||
from mdrsclient.cache import CacheInterface
|
from mdrsclient.cache import CacheInterface
|
||||||
from mdrsclient.config import ConfigFile
|
from mdrsclient.config import ConfigFile, ConfigInterface
|
||||||
from mdrsclient.connection import MDRSConnection
|
from mdrsclient.connection import MDRSConnection
|
||||||
from mdrsclient.exceptions import (
|
from mdrsclient.exceptions import (
|
||||||
IllegalArgumentException,
|
IllegalArgumentException,
|
||||||
@@ -18,12 +18,21 @@ from mdrsclient.utils import page_num_from_url
|
|||||||
|
|
||||||
|
|
||||||
class MdrsService:
|
class MdrsService:
|
||||||
def __init__(self, connection: MDRSConnection):
|
config_class: type[ConfigInterface] = ConfigFile
|
||||||
|
|
||||||
|
def __init__(self, connection: MDRSConnection, config_class: type[ConfigInterface] | None = None):
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
if config_class is not None:
|
||||||
|
self.config_class = config_class
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_connection(cls, remote: str, cache: CacheInterface | None = None) -> MDRSConnection:
|
def create_connection(
|
||||||
config = ConfigFile(remote)
|
cls, remote: str, cache: CacheInterface | None = None, config: ConfigInterface | None = None
|
||||||
|
) -> MDRSConnection:
|
||||||
|
if config is None:
|
||||||
|
config = ConfigFile(remote)
|
||||||
|
elif config.remote != remote:
|
||||||
|
raise IllegalArgumentException("Remote host parameter mismatch.")
|
||||||
if config.url is None:
|
if config.url is None:
|
||||||
raise MissingConfigurationException(f"Remote host `{remote}` is not found.")
|
raise MissingConfigurationException(f"Remote host `{remote}` is not found.")
|
||||||
return MDRSConnection(config.remote, config.url, cache=cache)
|
return MDRSConnection(config.remote, config.url, cache=cache)
|
||||||
|
|||||||
Reference in New Issue
Block a user