refactor: extract MdrsClient service layer for library portability

To improve the tool's portability as a Python library, the core logic
has been decoupled from the CLI interface. This allows developers to
programmatically interact with MDRS without relying on CLI-specific
argument parsing or local file-based caches.

- Introduce `MdrsClient` service layer to handle core operations.
- Abstract authentication state using `CacheInterface` and `InMemoryCache`.
- Migrate all CLI commands to utilize `MdrsClient` for execution.
- Separate `Doi` data model from API responses and move to `models/doi.py`.
- Update `README.md` to include Python API usage examples.
- Bump package version to 1.3.17.
This commit is contained in:
2026-07-02 13:07:18 +09:00
parent 809140dfbc
commit 36cad6db52
28 changed files with 736 additions and 215 deletions
+63 -1
View File
@@ -2,6 +2,7 @@ import dataclasses
import hashlib
import json
import os
from typing import Protocol, runtime_checkable
from pydantic import TypeAdapter, ValidationError
from pydantic.dataclasses import dataclass
@@ -16,7 +17,7 @@ from mdrsclient.utils import FileLock
class CacheData:
user: User | None = None
token: Token | None = None
laboratories: Laboratories = Laboratories()
laboratories: Laboratories = dataclasses.field(default_factory=Laboratories)
digest: str = ""
def clear(self) -> None:
@@ -43,6 +44,67 @@ class CacheData:
).hexdigest()
@runtime_checkable
class CacheInterface(Protocol):
@property
def token(self) -> Token | None: ...
@token.setter
def token(self, token: Token) -> None: ...
@token.deleter
def token(self) -> None: ...
@property
def user(self) -> User | None: ...
@user.setter
def user(self, user: User) -> None: ...
@user.deleter
def user(self) -> None: ...
@property
def laboratories(self) -> Laboratories: ...
@laboratories.setter
def laboratories(self, laboratories: Laboratories) -> None: ...
class InMemoryCache:
def __init__(self) -> None:
self.__data = CacheData()
@property
def token(self) -> Token | None:
return self.__data.token
@token.setter
def token(self, token: Token) -> None:
self.__data.token = token
@token.deleter
def token(self) -> None:
if self.__data.token is not None:
self.__data.token = None
@property
def user(self) -> User | None:
return self.__data.user
@user.setter
def user(self, user: User) -> None:
self.__data.user = user
@user.deleter
def user(self) -> None:
if self.__data.user is not None:
self.__data.user = None
@property
def laboratories(self) -> Laboratories:
return self.__data.laboratories
@laboratories.setter
def laboratories(self, laboratories: Laboratories) -> None:
self.__data.laboratories = laboratories
class CacheFile:
__serial: int
__cache_dir: str