Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 17 additions & 19 deletions packages/timenet-connectors/src/timenet_connectors/builder/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from timenet.cli.runner import run_cli
from timenet.cli.ui import console
from timenet.config import settings
from timenet.coordinator.plan import ParentStep, prepare_parents
from timenet.coordinator.plan import Step, prepare_parents
from timenet.engine import publish_pipeline, run_pipeline
from timenet.errors import (
TimeFValidationError,
Expand Down Expand Up @@ -195,21 +195,21 @@ def build_one( # ruff: ignore[too-many-arguments]
narrate: bool = False,
pinned_by: Mapping[DatasetRef, PayloadSource] | None = None,
) -> str:
"""Prepare a dataset's parent releases, then build the dataset.
"""Prepare a dataset's parents, then build or reuse its release.

A missing parent that ``fetch_from`` or ``build_parents`` does not provide fails the build before any download.
The build reuses an existing version unless ``force`` is true.
The build fails before downloading if it cannot copy or build a missing parent.
A new release must match every file record in ``pinned_by`` before publication.

Args:
dataset_id: The dataset id.
output: The output directory or a ``timenet://``, ``http(s)://``, or ``s3://`` registry URL.
dataset_id: The dataset ID.
output: A local directory or a ``timenet://``, ``http(s)://``, or ``s3://`` registry URL.
fetch_from: The registry, or its URI, to copy missing parent releases from.
build_parents: Build missing parent releases from their installed connectors.
force: Rebuild an existing version.
keep_cache: Keep the raw download cache after building.
isolation: Use isolation if true, this interpreter if false, or ``TIMENET_ISOLATION`` if ``None``.
isolation: Use a separate environment if true, this interpreter if false, or ``TIMENET_ISOLATION`` if ``None``.
narrate: Print status and download progress when isolation is off.
pinned_by: Releases that read this dataset's files, with file pins the build must match.
pinned_by: The expected file records for this release, grouped by the releases that read them.

Returns:
The committed version directory for a local output, or the published version.
Expand All @@ -219,14 +219,14 @@ def build_one( # ruff: ignore[too-many-arguments]
override = None if isolation is None else ("on" if isolation else "off")
isolated = settings(isolation=override).isolation == "on"

def build_parent(step: ParentStep) -> object:
builder = find_builder(step.reference.dataset_id)
def build_parent(step: Step) -> object:
builder = find_builder(step.release.dataset_id)
if builder is not None and not _in_repo(builder):
return _build_with(builder, step, target)
# Each parent builds like the dataset itself. An isolated child makes the plan again. The
# child finds every parent in the output, so it only opens the parents.
return _build(
step.reference.dataset_id,
step.release.dataset_id,
target,
output,
isolated=isolated,
Expand Down Expand Up @@ -282,25 +282,23 @@ def _in_repo(builder: BuilderBackend) -> bool:
return isinstance(builder, ConnectorBuilder) and type(builder).build is ConnectorBuilder.build


def _build_with(builder: BuilderBackend, step: ParentStep, target: WritableRegistry | Path) -> Path:
"""Build a parent through the builder backend that claims it, with the pins of its step.
def _build_with(builder: BuilderBackend, step: Step, target: WritableRegistry | Path) -> Path:
"""Build a parent release with its file pins through the given backend.

The plan already prepared the parents of this parent. So the backend gets no options for
parents.
``target`` must already hold the parents of ``step.release``.

Returns:
The committed version directory.

Raises:
TimeFValidationError: If the output is not local. A builder backend builds into a local
directory.
TimeFValidationError: If ``target`` is not a local ``Path``.
"""
if not isinstance(target, Path):
raise TimeFValidationError(
f"parent release {step.reference} builds through another builder backend, which writes to a "
f"parent release {step.release} builds through another builder backend, which writes to a "
"local directory; build it into a local output and publish it first"
)
return builder.build(step.reference.dataset_id, target, pinned_by=step.pinned_by or None)
return builder.build(step.release.dataset_id, target, pinned_by=step.pinned_by or None)


def _committed(dataset_id: str, target: WritableRegistry | Path, registry: WritableRegistry) -> str | None:
Expand Down
30 changes: 15 additions & 15 deletions packages/timenet-connectors/tests/test_build_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from timenet import builders
from timenet.connectors import BaseConnector
from timenet.coordinator.plan import ParentStep, plan_parents, prepare_parents
from timenet.coordinator.plan import Step, plan_parents, prepare_parents
from timenet.dataset import Record, RegularAxis, Signal, Source, TimeFDataset
from timenet.engine import open_parents, run_pipeline
from timenet.errors import (
Expand Down Expand Up @@ -93,8 +93,8 @@ def _cards(**parents: dict[str, DatasetRef]) -> dict:
return cards


def _no_build(step: ParentStep) -> None:
pytest.fail(f"the plan built {step.reference}")
def _no_build(step: Step) -> None:
pytest.fail(f"the plan built {step.release}")


def _install_cards(monkeypatch, **parents: dict[str, DatasetRef]) -> dict:
Expand All @@ -111,9 +111,9 @@ def _install_cards(monkeypatch, **parents: dict[str, DatasetRef]) -> dict:
@pytest.mark.parametrize(
("grand_in", "expected"),
[
("output", [ParentStep(PARENT, "build")]),
("origin", [ParentStep(GRAND, "fetch"), ParentStep(PARENT, "build")]),
("nowhere", [ParentStep(GRAND, "build"), ParentStep(PARENT, "build")]),
("output", [Step(PARENT, "build")]),
("origin", [Step(GRAND, "copy"), Step(PARENT, "build", depends_on=(GRAND,))]),
("nowhere", [Step(GRAND, "build"), Step(PARENT, "build", depends_on=(GRAND,))]),
],
)
def test_plan_uses_fetches_or_builds_each_parent_once_before_its_children(tmp_path, monkeypatch, grand_in, expected):
Expand Down Expand Up @@ -213,7 +213,7 @@ def test_a_declared_parent_needs_only_its_direct_payload_sources(tmp_path, monke

# DECLARED opens with SOURCE, its direct payload source, which a fetch of DECLARED brings; GRAND,
# SOURCE's own source, is not needed.
assert steps == ([] if where == "output" else [ParentStep(DECLARED, "fetch")])
assert steps == ([] if where == "output" else [Step(DECLARED, "copy")])
prepare_parents(CHILD.dataset_id, output, build=_no_build, fetch_from=origin)
for reference in (DECLARED, SOURCE):
assert output.exists(reference.dataset_id, str(reference.version))
Expand All @@ -230,7 +230,7 @@ def test_a_payload_source_that_is_later_a_declared_parent_brings_its_own_payload

steps = plan_parents(CHILD.dataset_id, output, build_parents=True)

assert steps == [ParentStep(GRAND, "build", pinned_by={SOURCE: pins})]
assert steps == [Step(GRAND, "build", pinned_by={SOURCE: pins})]


def test_the_plan_reads_each_card_from_the_backend_that_claims_the_dataset(tmp_path, monkeypatch):
Expand All @@ -243,7 +243,7 @@ def test_the_plan_reads_each_card_from_the_backend_that_claims_the_dataset(tmp_p

steps = plan_parents(CHILD.dataset_id, LocalRegistry(tmp_path), build_parents=True)

assert steps == [ParentStep(GRAND, "build"), ParentStep(PARENT, "build")]
assert steps == [Step(GRAND, "build"), Step(PARENT, "build", depends_on=(GRAND,))]
cards[PARENT.dataset_id] = cards[PARENT.dataset_id].model_copy(update={"dataset_version": "2.0.0"})
with pytest.raises(TimeFValidationError, match=r"test/parent@1\.0\.0 is missing .* builds version 2\.0\.0"):
plan_parents(CHILD.dataset_id, LocalRegistry(tmp_path), build_parents=True)
Expand Down Expand Up @@ -306,7 +306,7 @@ def test_a_pinned_source_is_rebuilt_in_staging_and_published_only_if_it_matches(

steps = plan_parents(CHILD.dataset_id, output, build_parents=True)

assert steps == [ParentStep(GRAND, "build", pinned_by={PARENT: pins})]
assert steps == [Step(GRAND, "build", pinned_by={PARENT: pins})]

def build(step):
run_pipeline(_Grand(), output.root, pinned_by=step.pinned_by)
Expand Down Expand Up @@ -338,7 +338,7 @@ def test_a_payload_source_that_two_releases_read_is_built_against_the_pins_of_bo

steps = plan_parents(CHILD.dataset_id, output, build_parents=True)

assert steps == [ParentStep(GRAND, "build", pinned_by=pins)]
assert steps == [Step(GRAND, "build", pinned_by=pins)]


def test_a_built_release_needs_only_its_payload_sources(tmp_path, monkeypatch):
Expand All @@ -351,14 +351,14 @@ def test_a_built_release_needs_only_its_payload_sources(tmp_path, monkeypatch):
plan_parents(PARENT.dataset_id, output)
# The source that PARENT reads builds against its pins.
assert plan_parents(PARENT.dataset_id, output, build_parents=True) == [
ParentStep(GRAND, "build", pinned_by={PARENT: pins})
Step(GRAND, "build", pinned_by={PARENT: pins})
]
# A rebuild of PARENT needs its declared parents instead, and pins the new files.
assert plan_parents(PARENT.dataset_id, output, build_parents=True, force_build=True) == [ParentStep(GRAND, "build")]
assert plan_parents(PARENT.dataset_id, output, build_parents=True, force_build=True) == [Step(GRAND, "build")]
# A registry that holds the source fills the gap: the local PARENT is reused and GRAND is copied.
origin = LocalRegistry(tmp_path / "origin")
origin.put_dataset(make_dataset())
assert plan_parents(PARENT.dataset_id, output, fetch_from=origin) == [ParentStep(PARENT, "fetch")]
assert plan_parents(PARENT.dataset_id, output, fetch_from=origin) == [Step(PARENT, "copy")]
prepare_parents(PARENT.dataset_id, output, build=_no_build, fetch_from=origin)
assert output.exists(GRAND.dataset_id, str(GRAND.version))

Expand All @@ -374,7 +374,7 @@ def test_a_damaged_parent_is_fetched_again_but_never_built_over(tmp_path, monkey
plan_parents(CHILD.dataset_id, output, build_parents=True)
assert caught.value.release == PARENT

assert plan_parents(CHILD.dataset_id, output, fetch_from=origin) == [ParentStep(PARENT, "fetch")]
assert plan_parents(CHILD.dataset_id, output, fetch_from=origin) == [Step(PARENT, "copy")]
prepare_parents(CHILD.dataset_id, output, build=_no_build, fetch_from=origin)
assert plan_parents(CHILD.dataset_id, output) == []

Expand Down
Loading
Loading