- Raise the floors for pydantic, pydantic-settings, python-dotenv and
isort to the releases they are tested against.
- Move the flit build backend to 4.x.
flake8 reported 49 findings and pyright 10, and two of them were real
bugs rather than matters of style. The rest were unused imports and
four functions over the complexity limit.
- Unlock a DOI folder with the id the DOI response carries, rather
than an attribute the model does not have, which raised
`AttributeError` on every locked DOI folder.
- Size the `ls` Size column from the sub-folder on the row rather than
from its parent, which pushed the later columns out of line.
- Share the path resolution and the destination checks between `cp`
and `mv`, and split the recursive download and the `ls` row
printing, bringing all four functions under the complexity limit.
- Accept a client built without a connection, which `config` and
`version` rely on, and report the reason if one is then asked for.
- Declare the config protocol's constructor for the type checker
alone, so the protocol keeps its guard against being instantiated.
- Remove 41 unused imports, and let flake8 accept black's spacing.
- Point pyright at the project's own environment, without which it
resolved no dependency and reported 124 findings that were not real.
- Cover the two fixes and the shared `cp`/`mv` paths with new tests.
Upload and download failures were printed and then forgotten: a batch
that lost files still exited 0, and a file the client could not write
was listed as if it had arrived. Nothing downstream could tell.
- Return a verdict from every transfer worker and raise once at the
end, so a run that lost a file exits 2.
- Raise the error when a downloaded file cannot be written locally,
instead of printing it and reporting the path as a success.
- Write a download beside its destination and move it into place once
complete, and refuse a destination that cannot be written, so a
failed transfer no longer leaves a truncated file behind.
- Carry on through the remaining sub-folders when a recursive download
loses a file or cannot create a folder locally.
- Name the file and give the reason in every failure message.
- Count a failure the API layer did not raise, such as a file that
disappeared between the directory walk and its turn to be sent.
- Extract the directory walk from `Uploader.upload` to keep it within
the complexity limit.
- Cover all of the above in `tests/test_transfer.py`.
A request can wait to be served for longer than the access token it was
sent with lives, and comes back refused for a token that was valid when
it left. Uploads that take minutes make that wait ordinary.
- tell an expired token apart from any other refusal by the code the
API returns, and retry only that one, once
- prefer a token another process left behind over minting a second: it
saves a round trip, and a rotating provider would retire one that is
still in use. Assign it rather than read it, since the setter is what
rewrites the session header the retry will carry
- settle the token before the request so the one compared afterwards is
the one that was actually sent
- report an overloaded server when the second attempt is refused too,
and let API errors out of the upload path with their own type
The login cache is shared by every mdrs process, but the refresh was
guarded by a lock that only reaches inside one. Concurrent runs each
sent the same refresh token, and a provider that rotates them accepts
the first and refuses the rest.
- hold a lock that spans processes across the whole read-refresh-
write, checking cheaply first so ordinary requests never take it
- write the cache through a temporary file: opening it for writing
truncates it, and a reader landing in that window found it empty
and threw the session away
- take the lock for every write, not just the refresh, so a login
running beside one cannot be silently reverted
- wait for a busy lock on Windows rather than giving up after the ten
attempts msvcrt allows, and retry the rename it refuses while a
reader still holds the file open
- bound the refresh request on its own, so a provider that goes quiet
cannot hold the lock indefinitely
- accept bare hostnames such as localhost, store URLs without the
trailing slash, and join download paths through one helper
Migrate package metadata and dependency configuration from Poetry to
PEP 621 standard using flit_core backend. Update documentation with
production and development setup instructions using venv, pip, or uv.
- update pyproject.toml to PEP 621 format with flit_core build-system
- bump minimum dependency versions to latest PyPI releases
- revise README.md with production and development setup guides
Historically, several versions in the changelog only had generic
version bump messages. This update fills in the missing details of
actual features and bug fixes by referencing the Git commit log.
- Add specific change logs for versions v1.3.3 through v1.3.15
- Include missing details for features like new command-line options
- Document bug fixes for API pagination, normalization, and downloads
Bump the package version to 1.3.18, upgrade dependencies,
consolidate module exports, add a unit test suite, and document
all changes.
- Bump package version to 1.3.18 in pyproject.toml
- Upgrade pydantic-settings to 2.14.2 and pyright to 1.1.411
- Consolidate package exports in mdrsclient/__init__.py
- Add a comprehensive unit test suite in tests/test_commands.py
- Document testing execution and add full history in CHANGELOG.md
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
Decouple CLI commands from internal helper logic and consolidate the
core file transfer operations in the service layer to improve library
portability.
- Make MdrsClient subclass MdrsService to inherit resource resolution.
- Remove all deprecated helper methods from BaseCommand.
- Move core upload and download logic to a new transfer module.
- Refactor all CLI commands to route actions through MdrsClient.
- Eliminate circular imports between client and CLI command modules.
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.
Retrieve the full Folder object from FoldersApi instead of using the
FolderSimple returned by find_sub_folder when resolving DOI subfolders.
This fixes a type checker error under the upgraded pyright, and avoids
a potential AttributeError at runtime due to FolderSimple lacking the
path attribute.
Also upgrade dependencies and bump version to 1.3.16 in pyproject.toml.
Support accessing repositories using DOI strings with optional subpaths
across ls, download, metadata, and file-metadata commands.
- Implement GET v3/doi/{id}/ API model and client calls
- Parse and resolve DOI paths into respective folder and files
- Extract common folder and file resolution logic to shared helpers
- Update README with example DOI-based shell commands
On macOS, local filenames and directory names may be in NFD encoding
(decomposed Unicode). Without normalization, files and folders are
created on the server with NFD names, inconsistent with the server's
NFC convention.
Apply normalize("NFC", ...) before sending names to the server in:
- FilesApi.create(): filename in multipart upload
- FilesApi.update(): filename in multipart upload
- UploadCommand: directory name in FoldersApi.create()
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>