Repository navigation
feat(sources)!: resolve a release through an ordered list of sources - #657
Merged
Merged
Conversation
This was referenced Nov 4, 2026
karhu
force-pushed
the
tnet-157/9-sources
branch
2 times, most recently
from
November 4, 2026 13:08
06647d6 to
4a28aae
Compare
This was referenced Nov 4, 2026
karhu
force-pushed
the
tnet-157/9-sources
branch
from
November 4, 2026 13:16
4a28aae to
504d5ea
Compare
karhu
force-pushed
the
tnet-157/9-sources
branch
from
November 4, 2026 13:34
504d5ea to
76e9b97
Compare
This was referenced Nov 4, 2026
karhu
force-pushed
the
tnet-157/9-sources
branch
from
November 4, 2026 14:58
76e9b97 to
6284ca1
Compare
karhu
marked this pull request as ready for review
November 4, 2026 15:21
karhu
commented
Nov 4, 2026
karhu
marked this pull request as draft
November 4, 2026 15:22
✅ CI checks passed
Updated for |
karhu
force-pushed
the
tnet-157/9-sources
branch
2 times, most recently
from
November 4, 2026 20:02
f54ac3d to
8ae7797
Compare
TimoStoff
reviewed
Nov 6, 2026
karhu
force-pushed
the
tnet-157/9-sources
branch
from
November 6, 2026 19:20
16b591e to
d549cd2
Compare
karhu
force-pushed
the
tnet-157/9-sources
branch
from
November 7, 2026 13:26
d549cd2 to
272bdcc
Compare
karhu
force-pushed
the
tnet-157/9-sources
branch
2 times, most recently
from
November 7, 2026 16:27
08de933 to
bd32dd9
Compare
karhu
force-pushed
the
tnet-157/9-sources
branch
from
November 7, 2026 17:40
bd32dd9 to
a73fc50
Compare
karhu
force-pushed
the
tnet-157/9-sources
branch
from
November 10, 2026 14:39
a73fc50 to
c1d1c26
Compare
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline finding, I checked whether the HTTP registry has the same body-streaming gap. It does not: in _http.py, stream_to keeps _reaching around the whole iter_bytes loop, and RemoteRegistry.download_version wraps all of download_version_files.
Extended reasoning...
I compared the HTTP path with the S3 finding. In RegistryHttpClient.stream_to, _reaching is entered in the same with statement as self._client.stream(...), so a transport error while reading the body is still wrapped. RemoteRegistry.download_version also wraps the parallel downloader in _reaching.
karhu
force-pushed
the
tnet-157/9-sources
branch
from
November 10, 2026 15:50
c1d1c26 to
5199c96
Compare
karhu
force-pushed
the
tnet-157/9-sources
branch
from
November 10, 2026 16:24
5199c96 to
0775580
Compare
A build and a read must find each release in one ordered list of sources: registries and the installed connectors. This change adds the parts that answer that question. The build and the client do not use them yet. The new module timenet.sources defines ReleaseSource, the union of BaseRegistry and InstalledConnectors. A source has the method resolve(dataset_id, version), where version is a Version, or None for the newest version of that source. A source gives one of four answers, a ReleaseLookup. FoundInRegistry names the version and the registry that serves the release. FoundInConnector keeps the InstalledBuilder that the lookup read: the backend and the card that build the release. Found is the union of the two. NotFound means that the source does not know the release. NotServed means that the source lists the release but does not serve its files. NotServed keeps the access page of each card that is not open, and access_error() turns it into a TimeNetAccessError. resolve_release() parses the version once and asks the sources in order. An exact version comes from the first source that serves it. Without a version (None or an empty string), the first source that serves the dataset gives its newest version. So a copy in the first source answers, and no later source is asked. "latest" is the highest version that any source provides, and the order decides only which source serves it. NotFound and NotServed fall through, and any other error stops the resolution. A remote registry that cannot be reached raises TimeNetRegistryError, which names the registry, not a raw httpx error. This also holds for the parallel download of a release. An S3 registry that cannot reach its bucket raises TimeNetRegistryError, which names the bucket. Every registry is now a source. BaseRegistry.resolve() reads the manifest. If the registry requires open access, it answers NotServed for a release whose effective access is not open. LocalRegistry.resolve() serves every release that it holds. It looks only for the manifest of the release, so a miss does not read every manifest in the registry. InstalledConnectors() answers with the version that the card of the connector declares. FoundInRegistry and NotServed keep the manifest that the source read to answer, in an optional manifest field. A caller can use it and does not fetch the same manifest again. The field takes no part in equality or in the repr. In every registry method that takes a version string, None, an empty string and "latest" all mean the newest version of that registry. split_ref() now keeps "latest", so org/x@latest and org/x are two different requests. A build reads both as the version of the card, because a connector builds only that version. The API docs generator now names a page after its full module path if both packages have a module with that name, because timenet.sources and timenet_connectors.sources both mapped to sources.md. BREAKING CHANGE: A remote registry that cannot be reached raises TimeNetRegistryError, not an httpx error. An S3 registry that cannot reach its bucket raises TimeNetRegistryError, not a botocore error. split_ref() returns "latest" for a reference that ends with @latest, not None. So a TimeNet method that gets such a reference and also a version argument raises TimeFValidationError. Part of TNET-157.
Say only what a caller of a release source and of resolve_release() 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.
karhu
force-pushed
the
tnet-157/9-sources
branch
from
November 10, 2026 17:41
0775580 to
3117782
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Adds
timenet.sources, which resolves a release through an ordered list of sources. The build and the client do not use it yet.Details
A build and a read must find each release in one ordered list of sources: registries and the installed connectors (TNET-157). Today each path asks its own places in its own order. This PR adds the parts that answer the question "which source provides this release?". The build and the client do not use them yet.
timenet.sourcesdefinesReleaseSource, the unionBaseRegistry | InstalledConnectors. Each source has one method:resolve(dataset_id, version), whereversionis aVersion, orNonefor the latest version.ReleaseLookup.FoundInRegistrynames the version and the registry that serves the release.FoundInConnectorkeeps theInstalledBuilderthat the lookup read, so a caller does not read the card again.Foundis the union of the two.NotFoundmeans that the source does not know the release.NotServedmeans that the source lists the release but does not serve its files, for example a credentialed release on a non-local registry.NotServed.access_error()turns it into aTimeNetAccessErrorthat names the access page of each card that is not open.resolve_release()parses the version once and asks the sources in order.BaseRegistry.resolve()reads the manifest. If the registry requires open access (requires_open_access), it answersNotServedfor a release whose effective access is not open.LocalRegistry.resolve()serves every release that it holds, and looks only for the manifest file of that release.InstalledConnectors()answers with the version that the card of the connector declares. For another version, it answersNotFound. A caller gets the declared version frombuilders.declared(), which raisesTimeNetVersionUnavailableError.TimeNetRegistryError, which names the registry or its storage, not anhttpxerror. This includes a "latest" lookup, which asks every source. It also includes a network drop during the parallel download of a release. An S3 registry that cannot reach its bucket raisesTimeNetRegistryError, which names the bucket, not a botocore error.FoundInRegistryandNotServedkeep the manifest that the source read, so a caller does not fetch it again.Design decisions
"latest"is the highest version that any source serves, and the order of the sources breaks a tie. So with@latest, a store that holds an old version does not hide a newer one.NotFoundandNotServedfall through to the next source. Any other error stops the resolution, so a broken source is never skipped in silence.Noneand an empty string mean no version, and"latest"asks every source.split_ref()returns"latest"fororg/name@latest, so the two requests stay apart.resolve_release()parses the version once, so no source parses it again.ReleaseSourceis a union, not a protocol. So code that holds an answer narrows it withisinstance, and the types rule out any other kind of source. A new kind of source extends the union.NotServedfor its newest version hides its older open versions, because no registry lists the versions that it serves.sources:timenet.sourcesandtimenet_connectors.sources. For such a pair, the API docs generator names each page after its full module path. The URL of the connectors page changes.Breaking changes
TimeNetRegistryError, not anhttpxerror.TimeNetRegistryError, not a botocore error.split_ref()returns"latest"fororg/name@latest, notNone.timenet_connectors.sourcesmoves fromapi/sources.mdtoapi/timenet_connectors.sources.md.Stack
Changes later in the stack:
Merge message