Skip to content

feat(client)!: download releases into the local registry - #662

Merged
karhu merged 5 commits into
tnet-157/13-sources-listfrom
tnet-157/14-download-store
Nov 10, 2026
Merged

karhu merged 5 commits into
tnet-157/13-sources-listfrom
tnet-157/14-download-store

Conversation

@karhu

@karhu karhu commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

The Stack

This stack covers refactoring the internal logic of how releases are resolved during read, download & build, in particular also in the context of parent releases / parent datasets. On top of that it refactors the public TimeNet APIs and CLI interfaces around these actions, to streamline the behavior across them.

This PR

download() copies a release into the store, through copy_release(), instead of into a separate storage folder.

Details

#661 gives the client a store, the first LocalRegistry of the list without read_only=True, but download() still copied into TIMENET_STORAGE as glue. This PR moves it into the store and makes it the way to repair a damaged copy.

  • download() resolves the release like open(). If the store holds a complete copy of the release and of each payload source that it needs, it returns the release directory at once.
  • Otherwise plan_download() in the coordinator plans the copies, and run_plan() runs them with copy_release(). The release comes from the first registry other than the store that provides the exact version. A payload source that this registry lacks comes from the first registry that provides it. So a release and its payload sources can come from different registries. The copy brings along each payload source that the store lacks, and it repairs a damaged copy in the store. A release needs only the payload sources that its manifest lists (the rule of feat(registry)!: copy a release between registries #649), so a payload source comes without its own payload sources.
  • copy_release() and CopyStep take force=True, which copies the requested release again into a local registry. For each release that it copies into a local registry, it sends BytesMoved events, so a build that copies a parent sends them too.
  • A read of a damaged copy in the store gets a hint that repairs it. If another registry provides the release, the hint names download('<release>'). If only the installed connectors provide it, the hint names the timenet-build command that builds it again.
  • timenet download shows its progress with progress_renderer(), as timenet-build build does: a status line for each release, and a progress bar in a terminal. If there is nothing to copy, it prints Nothing to do: '<id>' <version> is already in <store>. It prints the release directory on stdout.
  • BaseRegistry.download_version() goes away. TimeNet.download() and copy_release() replace it.
  • Size: about 1210 changed lines. The download, its repair rules and their tests read best as one change.

Design decisions

  • download() never builds. For a release that only InstalledConnectors() provides, it raises an error that names open() and load(). An installed connector can declare a newer version than any registry holds. Then download('org/name@latest') fails with the default list.
  • A registry over the directory of the store counts as the store, so the store is never copied onto itself. The planner and download() share that check. The check compares the folders on disk. So a symlink to the store also counts as the store. On a volume that ignores case, so does a path in another case.
  • A non-local registry refuses force=True, because a published version never changes. A different set of files or another card still raises TimeNetVersionConflictError.
  • A damaged copy outside the store gets no repair hint, because download() writes only into the store.

Breaking changes

  • download() copies into the store through copy_release(), and refuses a store copy with other files or another card.
  • download() copies each release from the first registry that provides it, so a release and its payload sources can come from different registries.
  • Without a version, download() takes the newest version of the first source that serves the dataset. With @latest, it asks every source in the list, so each registry must be reachable.
  • download() takes progress= events instead of progress_cb=.
  • The separate download storage folder is no longer used by download().
  • BaseRegistry.download_version() is gone. Use TimeNet.download() or copy_release().

Stack

Changes later in the stack:

Merge message

feat(client)!: download releases into the local registry

download() copies a release into the writable local registry through
copy_release(), instead of into a separate storage folder. It also
repairs a damaged copy there.

- The release comes from the first registry other than the local
  registry that provides it. Each missing payload source comes from
  the first registry that provides it.
- download() never builds. For a release that only the installed
  connectors provide, the error names open() and load().
- copy_release() takes force=True, which copies a release again into
  a local registry. A non-local registry refuses it.
- A read of a damaged local copy gets a hint that repairs it.

BREAKING CHANGE: download() writes into the local registry, takes
progress= instead of progress_cb=, and refuses a local copy with other
files or another card.

BREAKING CHANGE: BaseRegistry.download_version() is gone. Use
TimeNet.download() or copy_release().

@karhu
karhu added this pull request to stack #650 November 4, 2026 14:26
@karhu
karhu force-pushed the tnet-157/14-download-store branch 2 times, most recently from aa908c1 to e01986e Compare November 4, 2026 15:27
@karhu
karhu force-pushed the tnet-157/14-download-store branch from e01986e to cc51755 Compare November 4, 2026 15:31
@karhu
karhu force-pushed the tnet-157/14-download-store branch from cc51755 to 1c8dd29 Compare November 4, 2026 20:02
@karhu
karhu force-pushed the tnet-157/14-download-store branch 2 times, most recently from 74e5bcf to c0278e4 Compare November 7, 2026 17:40
@karhu
karhu force-pushed the tnet-157/14-download-store branch 2 times, most recently from 291cfc6 to 8c058c4 Compare November 7, 2026 22:40
@karhu karhu changed the title feat(client)!: download releases into the store feat(client)!: download releases into the local registry Nov 9, 2026
@karhu
karhu force-pushed the tnet-157/14-download-store branch from 8c058c4 to ea10ae3 Compare November 9, 2026 13:56
@karhu
karhu force-pushed the tnet-157/14-download-store branch from ea10ae3 to 9c7bf60 Compare November 9, 2026 16:18
@karhu
karhu force-pushed the tnet-157/14-download-store branch from 9c7bf60 to 48ebb07 Compare November 10, 2026 14:39

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread packages/timenet/src/timenet/client.py Resolved
@karhu
karhu force-pushed the tnet-157/14-download-store branch from 48ebb07 to 6247a74 Compare November 10, 2026 15:50
@karhu
karhu removed this pull request from stack #699 November 10, 2026 15:58

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review completed

Nothing new to post: everything this review found is already covered by existing comments on this pull request or didn't merit a separate one.

@karhu
karhu force-pushed the tnet-157/14-download-store branch from 6247a74 to a60e678 Compare November 10, 2026 16:24
@karhu
karhu added this pull request to stack #772 November 10, 2026 16:24

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new blocking issues. 1 optional suggestion (a nit or a note on pre-existing code) was found and not posted. Nothing in this review needs a push before merging.

download() now copies a release into the local registry, the first local
registry of the list that is not read-only. It resolves the release like
open(). Then plan_download() plans the copies, and run_plan() runs them.
The requested release and each payload source that the local registry
lacks or holds damaged come from the first registry other than the local
registry that provides them. So a release and its payload source can
come from two registries. If the local registry holds a complete copy of
the release, download() copies only the payload sources that the local
registry lacks. A client without a writable local registry raises
TimeFValidationError. A registry over the directory of the local
registry counts as the local registry, also through a symlink or through
a case variant of the name on a volume that ignores case. So the local
registry is never copied onto itself.

download() never builds. For a release that only InstalledConnectors()
provides, it raises TimeNetDatasetNotFoundError, which names open() and
load(). So download() of "latest" fails when an installed connector
declares a newer version than any registry holds. Without a version,
download() takes the first source that serves the dataset, as open()
does. For an exact version that no source provides, an installed
connector that builds another version gives
TimeNetVersionUnavailableError, as in open(). A registry that lists the
release but does not serve its files gives the same access error as
open(). Other errors name the local registry: force=True or a damaged
copy when only the local registry holds the release.

copy_release() takes force=True, which copies the requested release
again into a local registry. A complete payload source stays as it is.
Other files still raise TimeNetVersionConflictError. A registry that is
not local refuses force=True with TimeFValidationError, because it never
writes a published version again. For each release that it copies into a
local registry, copy_release() sends BytesMoved events with the release
as subject. So a build that copies a parent sends them too. A copy step
of a plan passes its force flag to copy_release().

A read of a damaged copy in the local registry ends its TimeNetFileError
with a hint that repairs it: "Copy an intact release into the local
registry with download('<release>')." when another registry provides the
release, or the timenet-build command that builds it again when only the
installed connectors provide it. A damaged copy outside the local
registry gets no hint.

timenet download shows the same output as timenet-build: a status line
when the copy of each release starts and ends, a progress bar for each
release in a terminal, and "Nothing to do" when the local registry
already holds the release and its payload sources. It prints the release
directory in the local registry to stdout. With --force, it copies the
release again, as download(force=True) does. The help of timenet cache
clear now says that --all removes the default local registry with its
downloads.

The interim download() into TIMENET_STORAGE, _reject_unhosted_access()
and BaseRegistry.download_version() are gone.

BREAKING CHANGE: download() copies into the local registry, refuses a
copy in the local registry with other files, and takes progress= events
instead of progress_cb=. BaseRegistry.download_version() is gone. Use
TimeNet.download() instead. download() of "latest" reads the newest
version of every source in the list, so it needs each registry to be
reachable.

Part of TNET-157.
Say only what a caller of download() and copy_release(force=...) needs to know.

Part of TNET-157.
Shorter docstrings for the code that this change adds or changes.

Part of TNET-157.
Drop tests that cannot fail or repeat other tests, and sharpen the rest.

Part of TNET-157.
The glue comments in cache.py now start with TODO(adrian), so a search
finds each one that is left. A new TODO(adrian) comment in cli/app.py
marks the glue that puts the --registry of timenet download after the
store in the list of sources.

Part of TNET-157.
@karhu
karhu force-pushed the tnet-157/14-download-store branch from a60e678 to b42d4af Compare November 10, 2026 17:41
@karhu
karhu merged commit 3f29a5a into main Nov 10, 2026
4 of 8 checks passed
@karhu
karhu deleted the tnet-157/14-download-store branch November 10, 2026 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants