From 2287b857bd29fe3907f7d9e87a900cb341c1a544 Mon Sep 17 00:00:00 2001 From: jiangxt2 Date: Tue, 6 Oct 2026 13:57:44 +0800 Subject: [PATCH 1/3] feat(data): stabilize source configuration and dataset references Signed-off-by: jiangxt2 --- ci/test-suites.json | 31 ++++ docs/STABILITY.md | 14 +- docs/architecture/version-policy.md | 6 +- docs/reference/support-matrix.md | 1 + pyproject.toml | 8 + src/tributo/data/contracts/storage.py | 6 +- src/tributo/data/refs.py | 12 +- src/tributo/data/source_config.py | 32 ++-- tests/data/test_source_contract_compat.py | 57 +++++++ tests/fixtures/data-source-contract-v1.json | 152 +++++++++++++++++++ tests/integration/test_dowhy_scipy_compat.py | 88 +++++++++++ tests/test_stability_inventory.py | 13 +- uv.lock | 115 +++++++++++--- 13 files changed, 486 insertions(+), 49 deletions(-) create mode 100644 tests/data/test_source_contract_compat.py create mode 100644 tests/fixtures/data-source-contract-v1.json create mode 100644 tests/integration/test_dowhy_scipy_compat.py diff --git a/ci/test-suites.json b/ci/test-suites.json index ac804e3c..5741208b 100644 --- a/ci/test-suites.json +++ b/ci/test-suites.json @@ -171,6 +171,37 @@ "log_contract": "pytest terminal output; skips and missing JUnit evidence fail the suite", "rationale": "Dedicated bounded shard for integration-named modules that exercise local contracts without external infrastructure." }, + { + "id": "causal-dependency-contract", + "owner": "training", + "domain": "dependencies", + "tier": "ci_fast", + "workflow": "matrix", + "owns_tests": true, + "entrypoint": ["python", "-m", "pytest"], + "args": [ + "tests/integration/test_dowhy_scipy_compat.py", + "-o", + "addopts=", + "-q", + "--tb=short" + ], + "extras": ["dev", "causal"], + "test_paths": ["tests/integration/test_dowhy_scipy_compat.py"], + "trigger_domains": [], + "trigger_paths": [ + "pyproject.toml", + "uv.lock", + "tests/integration/test_dowhy_scipy_compat.py" + ], + "requires": [], + "budget_seconds": 180, + "ci_allowed": true, + "forbid_skips": true, + "markers": ["ci_safe"], + "log_contract": "pytest output and JUnit evidence with no dependency skips", + "rationale": "Bounded real DoWhy estimation, refutation, and GCM calls validate the locked SciPy closure without services." + }, { "id": "runtime-image", "owner": "core", diff --git a/docs/STABILITY.md b/docs/STABILITY.md index 5da0dfe8..3a121d47 100644 --- a/docs/STABILITY.md +++ b/docs/STABILITY.md @@ -112,7 +112,8 @@ from the legacy setup-only propagation rule. | Module | Level | Notes | |--------|-------|-------| -| `tributo.data.source_config` — `SourceConfig` | `beta` | Strict in-memory contract; JSON is the built-in persisted format | +| `tributo.data.source_config` — canonical models and projection helpers | `stable` | Stable fields, defaults, JSON shapes, and projection semantics; `BuiltinSourceConfig`, `SourceConfig`, and `CanonicalSourceInput` use the same stable members | +| `tributo.data.source_config` — legacy conversion and `RawSourceConfig` | `beta` | Compatibility input conversion and unknown-source passthrough | | `tributo.data.provider` — `DataSourceProvider` | `beta` | Logical normalization/planning contract; `normalize()` and `plan()` drive canonical ingestion, while `open()`/`DatasetHandle` remain an independent Provider SPI | | `tributo.data.transform_ir` | `alpha` | Versioned engine-neutral ETL contract | | `tributo.data.transform_compiler` | `developer` | Internal Ray/Daft expression translation | @@ -121,7 +122,7 @@ from the legacy setup-only propagation rule. | `tributo.data.handle_adapters` | `alpha` | Explicit native-handle conversions with conversion evidence; never a routing fallback | | `tributo.data.contracts.handles` | `alpha` | Typed Ray and Daft handle contracts shared by ingestion and writing | | `tributo.data.contracts.modes` | `beta` | Canonical shared `WriteMode` contract; `tributo.data.base` remains a narrow re-export | -| `tributo.data.contracts.storage` | `beta` | Canonical shared `S3Config` contract; credentials are hidden from repr and identity material | +| `tributo.data.contracts.storage` | `stable` | Canonical shared `S3Config` contract; credentials are hidden from repr and identity material | | `tributo.data.writing` | `alpha` | Unified bounded-write Gateway package | | `tributo.data.writing.capabilities` | `alpha` | Native writer capability declarations | | `tributo.data.writing.contracts` | `alpha` | Credential-safe write requests, descriptors, receipts, and errors | @@ -133,7 +134,14 @@ from the legacy setup-only propagation rule. | `tributo.data.graph` | `beta` | Graph data abstraction (GNN; @PublicAPI says beta) | | `tributo.data.base` | `legacy` | Narrow compatibility re-export for `S3Config` and `WriteMode`; no reader, writer, registry, or Connector class | | `tributo.data.provider_registry` | `beta` | Data source provider registry | -| `tributo.data.refs` | `beta` | Data reference value objects | +| `tributo.data.refs` | `stable` | DatasetRef fields, canonical JSON digest, version-1 source identity, and Arrow schema fingerprint | + +Canonical configuration stability does not imply that a Provider, Gateway, +Binding, database, or engine combination is stable or supported. Provider +options retain their provider-owned meaning. Credentials belong to trusted +configuration only: repr hides designated credential fields, while model dumps +and validation details must be sanitized before public logging. DatasetRef and +source identity reject credential material rather than persist it. ### Distributed Lance vector indexing (tributo.vector_index.*) diff --git a/docs/architecture/version-policy.md b/docs/architecture/version-policy.md index d346be93..c7ba6b41 100644 --- a/docs/architecture/version-policy.md +++ b/docs/architecture/version-policy.md @@ -36,8 +36,10 @@ Every public symbol falls into one of these tiers. The canonical inventory is - `alpha` → `beta`: The API has been used in at least one real workload and the interface is unlikely to change significantly. -- `beta` → `stable`: The API has been stable for ≥ 2 minor versions with no - reported interface issues. +- `beta` → `stable`: The public contract, supported scope, and limitations are + documented; compatibility and relevant functional validation pass; no known + unresolved interface issues affect that scope. There is no minimum release + count or waiting period. - `stable` → `deprecated`: A replacement exists, the deprecation window has started, and the migration guide is published. diff --git a/docs/reference/support-matrix.md b/docs/reference/support-matrix.md index de95f67a..c27aebac 100644 --- a/docs/reference/support-matrix.md +++ b/docs/reference/support-matrix.md @@ -42,6 +42,7 @@ endpoint. | Capability | Status | Boundary | | --- | --- | --- | +| Canonical source configuration and DatasetRef identity | Stable | Existing JSON shapes, fields, defaults, projection, and identity version 1; Provider/Gateway/Binding execution and provider-private options keep their own contracts | | Local/S3 Parquet and CSV reads | Verified | Native Ray Data or Daft handle through one Gateway | | Local/S3 Iceberg reads | Verified | Built-in bindings use PyIceberg `>=0.11.1,<0.12.0` with `PyArrowFileIO`; Ray may push `row_filter` into the scan, Daft applies it as a lazy residual filter, and empty-table schema is preserved from Iceberg metadata; broader Catalog/delete-file matrix remains gated | | Local/S3 Lance reads | Verified | Native Ray Data or Daft table reader; numeric versions and tags are supported, Daft also supports as-of, and Iceberg snapshot references fail closed | diff --git a/pyproject.toml b/pyproject.toml index cf63d5bd..3e0c9182 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -333,6 +333,8 @@ disallow_untyped_defs = false [dependency-groups] dev = [ + # Select the compatible official wheel without exceeding DoWhy 0.14 constraints. + "scipy==1.15.3; sys_platform == 'darwin' and platform_machine == 'arm64' and python_version == '3.12'", "pytest-html>=4.2.0", "grpcio-tools>=1.60.0", "boto3>=1.42.91", @@ -348,3 +350,9 @@ dev = [ "ml-dtypes>=0.5.0", "skl2onnx>=1.17.0", ] + +[tool.uv.sources] +# The macOS 14 arm64 wheel fails PROPACK loading on recent macOS. +# Keep the same SciPy release and the upstream DoWhy requirements. +# https://github.com/scipy/scipy/issues/25635 +scipy = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", marker = "sys_platform == 'darwin' and platform_machine == 'arm64' and python_version == '3.12'" } diff --git a/src/tributo/data/contracts/storage.py b/src/tributo/data/contracts/storage.py index 44cacd33..52c85f6f 100644 --- a/src/tributo/data/contracts/storage.py +++ b/src/tributo/data/contracts/storage.py @@ -12,7 +12,7 @@ __all__ = ["S3Config"] -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class S3Config(StrictConfigModel): """S3 connection configuration, shared across data modules. @@ -21,6 +21,10 @@ class S3Config(StrictConfigModel): ``AWS_ACCESS_KEY_ID`` → access_key_id, ``AWS_SECRET_ACCESS_KEY`` → secret_access_key, ``AWS_REGION`` → region. + + The model stores supplied values; the consuming Binding resolves fallbacks. + Credentials are excluded from repr, but model dumps and validation details + are trusted configuration data and must not be exposed as public logs. """ access_key_id: Optional[str] = Field( diff --git a/src/tributo/data/refs.py b/src/tributo/data/refs.py index 39a94074..3e2e9be6 100644 --- a/src/tributo/data/refs.py +++ b/src/tributo/data/refs.py @@ -151,7 +151,7 @@ def _canonical_json(value: Any) -> str: ) -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") def digest(value: Any) -> str: """SHA-256 of the canonical JSON representation of *value*. @@ -162,7 +162,7 @@ def digest(value: Any) -> str: return hashlib.sha256(_canonical_json(value).encode("utf-8")).hexdigest() -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") def compute_ref_id( *, provider_id: str, @@ -314,13 +314,15 @@ def _uri_has_credentials(uri: str) -> bool: return False -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") def schema_fingerprint(schema: "pa.Schema") -> str: """SHA-256 of the canonical Arrow schema (field structure + metadata). Used to detect schema drift between runs without embedding the full schema in a manifest. Field order matters (reordering columns changes - the fingerprint); metadata is normalized by key order. + the fingerprint); schema-level metadata is normalized by key order. + Field metadata is not included. Type identity uses Arrow's type strings; + changing Arrow's type representation can therefore change the fingerprint. """ import pyarrow as pa @@ -347,7 +349,7 @@ def _bytes_to_str(value: bytes | str) -> str: return value.decode("utf-8") if isinstance(value, bytes) else str(value) -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") @dataclass(frozen=True) class DatasetRef: """Credential-free record of what data was used. diff --git a/src/tributo/data/source_config.py b/src/tributo/data/source_config.py index e206f97e..9f79b835 100644 --- a/src/tributo/data/source_config.py +++ b/src/tributo/data/source_config.py @@ -2,6 +2,13 @@ Replaces the hardcoded ``if data_type == "s3": ... elif data_type == "csv": ...`` dispatch in ``training/data_loader.py`` and ``inference/pipeline.py``. + +Canonical models, projection helpers, and the ``BuiltinSourceConfig``, +``SourceConfig``, and ``CanonicalSourceInput`` aliases are stable contracts. +Legacy normalizers and ``RawSourceConfig`` remain beta. Configuration accepts +trusted runtime input; raw dumps and Pydantic validation details are not safe +public logging payloads. Provider execution and private options keep their own +stability and capability boundaries. """ from __future__ import annotations @@ -34,7 +41,7 @@ _SqlDialect = Literal["clickhouse", "doris", "postgresql", "mysql"] -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class RayReadTaskOptions(StrictConfigModel): """Validated Ray task options supported by the Doris read Binding. @@ -63,7 +70,7 @@ def _validate_finite_num_cpus( return value -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ParquetSourceConfig(StrictConfigModel): """Parquet source (local filesystem or S3). @@ -80,7 +87,7 @@ class ParquetSourceConfig(StrictConfigModel): s3: S3Config | None = Field(default=None, repr=False) -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class CsvSourceConfig(StrictConfigModel): """CSV source (local filesystem or S3). @@ -97,7 +104,7 @@ class CsvSourceConfig(StrictConfigModel): columns: list[str] | None = None -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class SqlSourceConfig(StrictConfigModel): """Unified SQL data source for ClickHouse, Doris, PostgreSQL, and MySQL. @@ -191,7 +198,7 @@ def _validate_protocol(self) -> "SqlSourceConfig": # --------------------------------------------------------------------------- -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class SqlPartitioning(BaseModel): """Performance hint for SQL result partitioning. @@ -214,7 +221,7 @@ def _validate_mode(self) -> "SqlPartitioning": return self -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class IcebergSourceConfig(StrictConfigModel): """Iceberg table source. @@ -253,7 +260,7 @@ class IcebergSourceConfig(StrictConfigModel): SourceConfig = BuiltinSourceConfig -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ProviderSourceConfig(StrictConfigModel): """Target ``provider/uri`` canonical source shape. @@ -264,9 +271,10 @@ class ProviderSourceConfig(StrictConfigModel): uri: Canonical URI of the bounded data source (``s3://``, local path, or a dialect-specific connection reference). options: Provider-validated options (format options, table - references, SQL query digest, etc.). May carry credentials — - redaction guarantees they never reach ``repr``, logs, errors, - ``DatasetRef`` or benchmark output. + references, SQL query digest, etc.). May carry runtime credentials + and are excluded from ``repr``. Persisted source identities are + credential-free; ordinary model dumps and validation details + must remain inside the trusted configuration boundary. """ provider: str = Field(min_length=1) @@ -289,7 +297,7 @@ def _provider_projection_option(source: ProviderSourceConfig) -> str | None: return resolve_provider(source).projection_option_name -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") def source_projection(source: CanonicalSourceInput) -> list[str] | None: """Return the provider-native projection configured on ``source``. @@ -314,7 +322,7 @@ def source_projection(source: CanonicalSourceInput) -> list[str] | None: return list(value) if value else None -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") def apply_source_projection( source: CanonicalSourceInput, columns: list[str] ) -> CanonicalSourceInput: diff --git a/tests/data/test_source_contract_compat.py b/tests/data/test_source_contract_compat.py new file mode 100644 index 00000000..37522383 --- /dev/null +++ b/tests/data/test_source_contract_compat.py @@ -0,0 +1,57 @@ +"""Persisted v1 source shapes and identities survive API stabilization.""" + +from __future__ import annotations + +import json +from dataclasses import asdict +from pathlib import Path +from typing import Any + +import pyarrow as pa +import pytest +from pydantic import TypeAdapter + +from tributo.data.base import S3Config as ReexportedS3Config +from tributo.data.contracts.storage import S3Config +from tributo.data.refs import DatasetRef, compute_ref_id, schema_fingerprint +from tributo.data.source_config import CanonicalSourceInput + +_FIXTURE = json.loads( + ( + Path(__file__).resolve().parents[1] + / "fixtures" + / "data-source-contract-v1.json" + ).read_text(encoding="utf-8") +) + + +@pytest.mark.parametrize("record", _FIXTURE["sources"]) +def test_persisted_canonical_source_shape(record: dict[str, Any]) -> None: + adapter: TypeAdapter[CanonicalSourceInput] = TypeAdapter(CanonicalSourceInput) + model = adapter.validate_json(json.dumps(record["input"])) + assert model.model_dump(mode="json") == record["expected"] + reloaded = adapter.validate_json(model.model_dump_json()) + assert reloaded.model_dump(mode="json") == record["expected"] + + +def test_persisted_reference_identity_v1() -> None: + identity = _FIXTURE["identity"] + schema = pa.schema( + [pa.field("x", pa.float64()), pa.field("y", pa.int64(), nullable=False)], + metadata={"version": "1", "owner": "tributo"}, + ) + assert compute_ref_id(**identity) == _FIXTURE["ref_id"] + assert schema_fingerprint(schema) == _FIXTURE["schema_fingerprint"] + serialized = { + "ref_id": _FIXTURE["ref_id"], + "provider_id": identity["provider_id"], + "uri": identity["canonical_uri"], + "schema_fingerprint": _FIXTURE["schema_fingerprint"], + "row_count": 3, + "provenance": "snapshot-7", + } + assert asdict(DatasetRef(**json.loads(json.dumps(serialized)))) == serialized + + +def test_storage_config_compatibility_reexport() -> None: + assert ReexportedS3Config is S3Config diff --git a/tests/fixtures/data-source-contract-v1.json b/tests/fixtures/data-source-contract-v1.json new file mode 100644 index 00000000..71c4fd8b --- /dev/null +++ b/tests/fixtures/data-source-contract-v1.json @@ -0,0 +1,152 @@ +{ + "contract_version": 1, + "sources": [ + { + "input": { + "type": "parquet", + "path": "s3://warehouse/train.parquet", + "columns": [ + "x", + "y" + ], + "s3": { + "region": "us-east-1" + } + }, + "expected": { + "type": "parquet", + "path": "s3://warehouse/train.parquet", + "columns": [ + "x", + "y" + ], + "s3": { + "access_key_id": null, + "secret_access_key": null, + "endpoint": null, + "region": "us-east-1" + } + } + }, + { + "input": { + "type": "csv", + "path": "/data/train.csv" + }, + "expected": { + "type": "csv", + "path": "/data/train.csv", + "s3": null, + "columns": null + } + }, + { + "input": { + "type": "sql", + "dialect": "doris", + "database": "analytics", + "table": "training", + "partitioning": { + "mode": "parallel", + "column": "id", + "num_partitions": 4 + }, + "ray_remote_args": { + "num_cpus": 1, + "scheduling_strategy": "SPREAD", + "max_retries": 2 + } + }, + "expected": { + "type": "sql", + "dialect": "doris", + "host": null, + "port": null, + "http_port": null, + "flight_port": null, + "database": "analytics", + "database_schema": null, + "user": null, + "password": null, + "sql": "", + "table": "training", + "protocol": "mysql", + "tablet_size": null, + "on_query_plan_error": null, + "ray_remote_args": { + "num_cpus": 1, + "scheduling_strategy": "SPREAD", + "max_retries": 2 + }, + "params": null, + "columns": null, + "partitioning": { + "mode": "parallel", + "column": "id", + "num_partitions": 4, + "bound_strategy": "min-max" + } + } + }, + { + "input": { + "type": "iceberg", + "catalog": "warehouse", + "table": "analytics.training", + "snapshot_id": 7, + "selected_fields": [ + "x", + "y" + ] + }, + "expected": { + "type": "iceberg", + "catalog": "warehouse", + "table": "analytics.training", + "catalog_properties": {}, + "s3": null, + "snapshot_id": 7, + "row_filter": null, + "selected_fields": [ + "x", + "y" + ] + } + }, + { + "input": { + "provider": "tributo.parquet", + "uri": "s3://warehouse/train.parquet", + "options": { + "columns": [ + "x", + "y" + ] + } + }, + "expected": { + "provider": "tributo.parquet", + "uri": "s3://warehouse/train.parquet", + "options": { + "columns": [ + "x", + "y" + ] + } + } + } + ], + "identity": { + "provider_id": "tributo.parquet", + "canonical_uri": "s3://warehouse/train.parquet", + "result_affecting_options": { + "columns": [ + "x", + "y" + ] + }, + "version": 1 + }, + "ref_id": "6d1b479b80c79eb1027fb2da97a35d6e34d78de347810ac70e3a674e9586ca14", + "schema_fingerprint": "cf41c4b6f371f9f1783e2a3ad74a78bfa94f5aeb0e4274e215414ee2f2d14f75" +} diff --git a/tests/integration/test_dowhy_scipy_compat.py b/tests/integration/test_dowhy_scipy_compat.py new file mode 100644 index 00000000..1b0c3d8b --- /dev/null +++ b/tests/integration/test_dowhy_scipy_compat.py @@ -0,0 +1,88 @@ +"""Real, bounded DoWhy calls for the locked SciPy dependency closure. + +Optional DoWhy imports remain inside tests so dev-only collection does not +require the causal extra. The manifest owns execution with that extra. +""" + +from __future__ import annotations + +import numpy as np +import pandas as pd +import pytest + +# DoWhy 0.14 contains invalid backslash escapes in Python source docstrings. +# Scope this compilation warning filter to these upstream dependency checks. +pytestmark = pytest.mark.filterwarnings("ignore:invalid escape sequence:SyntaxWarning") + + +def test_linear_estimation_and_placebo_refutation() -> None: + from dowhy import CausalModel + + rng = np.random.default_rng(7) + confounder = rng.normal(size=400) + treatment = rng.binomial(1, 0.5, size=400) + frame = pd.DataFrame( + { + "confounder": confounder, + "treatment": treatment, + "outcome": 3.0 * treatment + 2.0 * confounder, + } + ) + model = CausalModel( + data=frame, + treatment="treatment", + outcome="outcome", + common_causes=["confounder"], + ) + estimand = model.identify_effect(proceed_when_unidentifiable=True) + estimate = model.estimate_effect(estimand, method_name="backdoor.linear_regression") + refutation = model.refute_estimate( + estimand, + estimate, + method_name="placebo_treatment_refuter", + random_seed=7, + num_simulations=10, + n_jobs=1, + ) + + assert float(estimate.value) == pytest.approx(3.0, abs=1e-8) + assert np.isfinite(float(refutation.new_effect)) + assert abs(float(refutation.new_effect)) < 0.5 + + +def test_gcm_fit_attribution_and_counterfactual( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import networkx as nx + from dowhy import gcm + from dowhy.gcm.shapley import ShapleyConfig + + monkeypatch.setattr(gcm.config, "default_n_jobs", 1) + monkeypatch.setattr(gcm.config, "show_progress_bars", False) + rng = np.random.default_rng(11) + cause = rng.normal(size=120) + frame = pd.DataFrame({"cause": cause, "effect": 2.0 * cause}) + model = gcm.InvertibleStructuralCausalModel(nx.DiGraph([("cause", "effect")])) + model.set_causal_mechanism("cause", gcm.EmpiricalDistribution()) + model.set_causal_mechanism( + "effect", gcm.AdditiveNoiseModel(gcm.ml.create_linear_regressor()) + ) + gcm.fit(model, frame) + anomaly = pd.DataFrame({"cause": [6.0], "effect": [12.0]}) + attributions = gcm.attribute_anomalies( + model, + "effect", + anomaly, + num_distribution_samples=100, + shapley_config=ShapleyConfig(num_permutations=4, n_jobs=1), + ) + counterfactual = gcm.counterfactual_samples( + model, + {"cause": lambda values: np.zeros_like(values)}, + observed_data=anomaly, + ) + + assert set(attributions) == {"cause", "effect"} + assert all(np.asarray(values).shape == (1,) for values in attributions.values()) + assert all(np.isfinite(values).all() for values in attributions.values()) + np.testing.assert_allclose(counterfactual["effect"], [0.0], atol=1e-8) diff --git a/tests/test_stability_inventory.py b/tests/test_stability_inventory.py index 16850e35..eab96132 100644 --- a/tests/test_stability_inventory.py +++ b/tests/test_stability_inventory.py @@ -83,14 +83,14 @@ # Data — beta "tributo.data.source_config": "beta", "tributo.data.provider_registry": "beta", - "tributo.data.refs": "beta", + "tributo.data.refs": "stable", # Data — alpha candidate ingestion contract "tributo.data.scan_plan": "developer", "tributo.data.ingestion": "alpha", "tributo.data.handle_adapters": "alpha", "tributo.data.contracts.handles": "alpha", "tributo.data.contracts.modes": "beta", - "tributo.data.contracts.storage": "beta", + "tributo.data.contracts.storage": "stable", "tributo.data.transform_ir": "alpha", # Data — beta "tributo.data.provider": "beta", @@ -209,6 +209,15 @@ #: list the exceptions here. Keys are ``module.symbol``, values are the #: symbol-specific stability. _SYMBOL_OVERRIDES: dict[str, str] = { + "tributo.data.source_config.RayReadTaskOptions": "stable", + "tributo.data.source_config.ParquetSourceConfig": "stable", + "tributo.data.source_config.CsvSourceConfig": "stable", + "tributo.data.source_config.SqlSourceConfig": "stable", + "tributo.data.source_config.SqlPartitioning": "stable", + "tributo.data.source_config.IcebergSourceConfig": "stable", + "tributo.data.source_config.ProviderSourceConfig": "stable", + "tributo.data.source_config.source_projection": "stable", + "tributo.data.source_config.apply_source_projection": "stable", "tributo.plugin.validate_distributed_algorithm_descriptor": "alpha", "tributo.config.AlgorithmExecutionConfig": "alpha", "tributo.config.AlgorithmInputConfig": "alpha", diff --git a/uv.lock b/uv.lock index 745cfdd4..fc7ed4d8 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.12, <3.14" resolution-markers = [ "python_full_version >= '3.13' and platform_machine != 's390x' and sys_platform == 'win32'", @@ -317,7 +317,8 @@ dependencies = [ { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine != 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin')" }, { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and platform_machine != 'x86_64') or (python_full_version >= '3.13' and sys_platform != 'darwin')" }, { name = "scikit-learn" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/37/e34b70e97b83393d98bb53cdfcf0213af29ac5932d6752bbf2abbda5b998/bayesian-optimization-1.4.3.tar.gz", hash = "sha256:f9a448e1b52d961301cbc953ce4199709f6c26b1c88994c9b4dadb7752a64550", size = 27803, upload-time = "2023-04-25T21:58:35.41Z" } @@ -382,7 +383,8 @@ dependencies = [ { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and platform_machine != 'x86_64') or (python_full_version >= '3.13' and sys_platform != 'darwin')" }, { name = "pandas" }, { name = "plotly" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "six" }, ] @@ -413,7 +415,8 @@ dependencies = [ { name = "pandas" }, { name = "pydot" }, { name = "scikit-learn" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "statsmodels" }, { name = "tqdm" }, @@ -539,7 +542,8 @@ dependencies = [ { name = "cffi" }, { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine != 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin')" }, { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and platform_machine != 'x86_64') or (python_full_version >= '3.13' and sys_platform != 'darwin')" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/81/e2/47f692161779dbd98876015de934943effb667a014e6f79a6d746b3e4c2a/clarabel-0.11.1.tar.gz", hash = "sha256:e7c41c47f0e59aeab99aefff9e58af4a8753ee5269bbeecbd5526fc6f41b9598", size = 253949, upload-time = "2025-06-11T16:49:05.864Z" } @@ -796,7 +800,8 @@ dependencies = [ { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine != 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin')" }, { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and platform_machine != 'x86_64') or (python_full_version >= '3.13' and sys_platform != 'darwin')" }, { name = "osqp" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "scs" }, ] @@ -984,7 +989,8 @@ dependencies = [ { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and platform_machine != 'x86_64') or (python_full_version >= '3.13' and sys_platform != 'darwin')" }, { name = "pandas" }, { name = "scikit-learn" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "statsmodels" }, { name = "sympy" }, @@ -1018,7 +1024,8 @@ dependencies = [ { name = "packaging" }, { name = "pandas" }, { name = "scikit-learn" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "shap" }, { name = "sparse" }, @@ -1852,7 +1859,8 @@ dependencies = [ { name = "narwhals" }, { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine != 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin')" }, { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and platform_machine != 'x86_64') or (python_full_version >= '3.13' and sys_platform != 'darwin')" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/63/8e/4db5e29290d7e619c307fdb8dab0a0514090af2ce3ec483050e024ec6126/lightgbm-4.7.0.tar.gz", hash = "sha256:f8e20f682c9aabd000bcf4a7ed8aa6f473c1adfecccae34ec24e823d156f4af0", size = 1792896, upload-time = "2026-07-18T21:00:56.139Z" } @@ -2107,7 +2115,8 @@ dependencies = [ { name = "pandas" }, { name = "pyarrow" }, { name = "scikit-learn" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "sqlalchemy" }, { name = "waitress", marker = "sys_platform == 'win32'" }, @@ -2197,7 +2206,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine != 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin')" }, { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and platform_machine != 'x86_64') or (python_full_version >= '3.13' and sys_platform != 'darwin')" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/92/e7/def9d879d424bc934e9e43b3455e28cd2ac76ace81cdc94be803ea78d29f/momentchi2-0.1.8.tar.gz", hash = "sha256:95d4becfd181d447c0281292c5827d74a09b04c38bd312da48a8a08932e8de1f", size = 11748, upload-time = "2021-08-27T18:56:23.571Z" } @@ -3053,7 +3063,8 @@ dependencies = [ { name = "joblib" }, { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine != 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin')" }, { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and platform_machine != 'x86_64') or (python_full_version >= '3.13' and sys_platform != 'darwin')" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "setuptools" }, ] @@ -4212,7 +4223,8 @@ dependencies = [ { name = "joblib" }, { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine != 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin')" }, { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and platform_machine != 'x86_64') or (python_full_version >= '3.13' and sys_platform != 'darwin')" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "threadpoolctl" }, ] @@ -4242,7 +4254,6 @@ resolution-markers = [ "python_full_version < '3.13' and sys_platform == 'win32'", "python_full_version < '3.13' and sys_platform == 'emscripten'", "python_full_version < '3.13' and platform_machine == 'x86_64' and sys_platform == 'darwin'", - "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'", "python_full_version < '3.13' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -4251,8 +4262,6 @@ dependencies = [ sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, - { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, - { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, @@ -4260,8 +4269,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, - { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, @@ -4269,8 +4276,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, - { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, - { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, @@ -4279,6 +4284,62 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, ] +[[package]] +name = "scipy" +version = "1.15.3" +source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" } +resolution-markers = [ + "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" } }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6" }, +] + +[package.metadata] +requires-dist = [ + { name = "array-api-strict", marker = "extra == 'test'", specifier = ">=2.0,<2.1.1" }, + { name = "asv", marker = "extra == 'test'" }, + { name = "cython", marker = "extra == 'test'" }, + { name = "cython-lint", marker = "extra == 'dev'", specifier = ">=0.12.2" }, + { name = "doit", marker = "extra == 'dev'", specifier = ">=0.36.0" }, + { name = "gmpy2", marker = "extra == 'test'" }, + { name = "hypothesis", marker = "extra == 'test'", specifier = ">=6.30" }, + { name = "intersphinx-registry", marker = "extra == 'doc'" }, + { name = "jupyterlite-pyodide-kernel", marker = "extra == 'doc'" }, + { name = "jupyterlite-sphinx", marker = "extra == 'doc'", specifier = ">=0.19.1" }, + { name = "jupytext", marker = "extra == 'doc'" }, + { name = "matplotlib", marker = "extra == 'doc'", specifier = ">=3.5" }, + { name = "meson", marker = "extra == 'test'" }, + { name = "mpmath", marker = "extra == 'test'" }, + { name = "mypy", marker = "extra == 'dev'", specifier = "==1.10.0" }, + { name = "myst-nb", marker = "extra == 'doc'" }, + { name = "ninja", marker = "sys_platform != 'emscripten' and extra == 'test'" }, + { name = "numpy", specifier = ">=1.23.5,<2.5" }, + { name = "numpydoc", marker = "extra == 'doc'" }, + { name = "pooch", marker = "extra == 'doc'" }, + { name = "pooch", marker = "extra == 'test'" }, + { name = "pycodestyle", marker = "extra == 'dev'" }, + { name = "pydata-sphinx-theme", marker = "extra == 'doc'", specifier = ">=0.15.2" }, + { name = "pydevtool", marker = "extra == 'dev'" }, + { name = "pytest", marker = "extra == 'test'" }, + { name = "pytest-cov", marker = "extra == 'test'" }, + { name = "pytest-timeout", marker = "extra == 'test'" }, + { name = "pytest-xdist", marker = "extra == 'test'" }, + { name = "rich-click", marker = "extra == 'dev'" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.0.292" }, + { name = "scikit-umfpack", marker = "extra == 'test'" }, + { name = "sphinx", marker = "extra == 'doc'", specifier = ">=5.0.0,<8.0.0" }, + { name = "sphinx-copybutton", marker = "extra == 'doc'" }, + { name = "sphinx-design", marker = "extra == 'doc'", specifier = ">=0.4.0" }, + { name = "threadpoolctl", marker = "extra == 'test'" }, + { name = "types-psutil", marker = "extra == 'dev'" }, + { name = "typing-extensions", marker = "extra == 'dev'" }, +] +provides-extras = ["test", "doc", "dev"] + [[package]] name = "scipy" version = "1.17.1" @@ -4336,7 +4397,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine != 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin')" }, { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and platform_machine != 'x86_64') or (python_full_version >= '3.13' and sys_platform != 'darwin')" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9e/59/5cb7f9612a5a3ff6efd4ab2d899902a536cc5974a7edb589084c5577291c/scs-3.2.11.tar.gz", hash = "sha256:2a5455cf2093d07f84f2f848c199faed52e79cdb3a11fe250b5622b6bbac4913", size = 1691825, upload-time = "2026-01-09T17:53:54.074Z" } @@ -4377,7 +4439,8 @@ dependencies = [ { name = "packaging" }, { name = "pandas" }, { name = "scikit-learn" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "slicer" }, { name = "tqdm" }, @@ -4529,7 +4592,8 @@ dependencies = [ { name = "packaging" }, { name = "pandas" }, { name = "patsy" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/81/e8d74b34f85285f7335d30c5e3c2d7c0346997af9f3debf9a0a9a63de184/statsmodels-0.14.6.tar.gz", hash = "sha256:4d17873d3e607d398b85126cd4ed7aad89e4e9d89fc744cdab1af3189a996c2a", size = 20689085, upload-time = "2025-12-05T23:08:39.522Z" } @@ -4948,6 +5012,7 @@ dev = [ { name = "pytest-metadata" }, { name = "pytest-timeout" }, { name = "redis" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine == 'arm64' and sys_platform == 'darwin'" }, { name = "skl2onnx" }, ] @@ -5051,6 +5116,7 @@ dev = [ { name = "pytest-metadata", specifier = ">=3.1.1" }, { name = "pytest-timeout", specifier = ">=2.4.0" }, { name = "redis", specifier = ">=8.0.0" }, + { name = "scipy", marker = "python_full_version == '3.12.*' and platform_machine == 'arm64' and sys_platform == 'darwin'", url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, { name = "skl2onnx", specifier = ">=1.17.0" }, ] @@ -5359,7 +5425,8 @@ dependencies = [ { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine != 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin')" }, { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and platform_machine != 'x86_64') or (python_full_version >= '3.13' and sys_platform != 'darwin')" }, { name = "nvidia-nccl-cu12", marker = "sys_platform == 'linux'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and platform_machine == 'x86_64') or (python_full_version < '3.13' and sys_platform != 'darwin')" }, + { name = "scipy", version = "1.15.3", source = { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl" }, marker = "python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fd/41/846d4de2b8fc694073fd3ac5052caf68caa1ea11cb7fa32d7ad9c049b232/xgboost-3.3.0.tar.gz", hash = "sha256:58bcb8a4cace648cdab7b94fa4f16d2c9ff26d90dd4d26907168106fa06d8746", size = 1224702, upload-time = "2026-06-17T21:26:50.846Z" } From 3adc3f494401065fab074e1a55d87f5ed9be15cc Mon Sep 17 00:00:00 2001 From: jiangxt2 Date: Tue, 6 Oct 2026 14:12:09 +0800 Subject: [PATCH 2/3] fix(ci): select causal dependency regression suite Signed-off-by: jiangxt2 --- ci/test-suites.json | 18 ++++++++++++------ tests/test_ci_test_plan.py | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/ci/test-suites.json b/ci/test-suites.json index 5741208b..67293c8b 100644 --- a/ci/test-suites.json +++ b/ci/test-suites.json @@ -6,6 +6,16 @@ "nightly_total_seconds": 1800 }, "path_rules": [ + { + "name": "dependency-closure", + "patterns": ["pyproject.toml", "uv.lock"], + "domains": ["code", "docs", "storage", "dependencies"] + }, + { + "name": "causal-dependency-tests", + "patterns": ["tests/integration/test_dowhy_scipy_compat.py"], + "domains": ["code", "storage", "dependencies"] + }, { "name": "ci-policy", "patterns": [ @@ -188,12 +198,8 @@ ], "extras": ["dev", "causal"], "test_paths": ["tests/integration/test_dowhy_scipy_compat.py"], - "trigger_domains": [], - "trigger_paths": [ - "pyproject.toml", - "uv.lock", - "tests/integration/test_dowhy_scipy_compat.py" - ], + "trigger_domains": ["dependencies"], + "trigger_paths": [], "requires": [], "budget_seconds": 180, "ci_allowed": true, diff --git a/tests/test_ci_test_plan.py b/tests/test_ci_test_plan.py index a121b0ec..55674032 100644 --- a/tests/test_ci_test_plan.py +++ b/tests/test_ci_test_plan.py @@ -121,6 +121,30 @@ def test_public_api_generator_change_selects_documentation_gate( ) +@pytest.mark.parametrize( + "path", + ( + "pyproject.toml", + "uv.lock", + "tests/integration/test_dowhy_scipy_compat.py", + ), +) +def test_dependency_change_selects_real_causal_ci_suite( + manifest: ci_test_plan.Manifest, path: str +) -> None: + plan = ci_test_plan.build_plan( + manifest, event="pull_request", mode="pr", changed_paths=[path] + ) + + assert "causal-dependency-contract" in plan["selected"]["ci_fast"] + assert {"suite": "causal-dependency-contract"} in plan["matrix"]["include"] + suite = manifest.suite("causal-dependency-contract") + assert suite.extras == ("dev", "causal") + assert suite.forbid_skips + assert plan["run_unit"] is True + assert plan["run_docs"] is (path != "tests/integration/test_dowhy_scipy_compat.py") + + def test_storage_change_selects_bounded_ci_and_reports_external_validation( manifest: ci_test_plan.Manifest, ) -> None: From f247f924d87d76128c28d102f0491be724418066 Mon Sep 17 00:00:00 2001 From: jiangxt2 Date: Tue, 6 Oct 2026 13:59:15 +0800 Subject: [PATCH 3/3] feat(exporting): stabilize bundle storage contracts Signed-off-by: jiangxt2 --- docs/STABILITY.md | 37 ++++++-- docs/reference/api/model-lifecycle.md | 20 +++++ docs/reference/support-matrix.md | 5 +- src/tributo/_common/storage_profiles.py | 15 ++-- src/tributo/exceptions.py | 4 +- src/tributo/exporting/bundle_reader.py | 11 ++- src/tributo/exporting/manifest.py | 14 +-- src/tributo/exporting/models.py | 40 +++++---- src/tributo/exporting/publisher.py | 14 ++- src/tributo/exporting/repository.py | 12 +-- tests/test_stability_inventory.py | 32 +++++-- .../exporters/test_storage_contract_scope.py | 88 +++++++++++++++++++ 12 files changed, 236 insertions(+), 56 deletions(-) create mode 100644 tests/training/exporters/test_storage_contract_scope.py diff --git a/docs/STABILITY.md b/docs/STABILITY.md index 3a121d47..00b19ff8 100644 --- a/docs/STABILITY.md +++ b/docs/STABILITY.md @@ -157,13 +157,15 @@ source identity reject credential material rather than persist it. | Module | Level | Notes | |--------|-------|-------| | `tributo.exporting.service` — `BundleExportService` | `beta` | Primary export orchestration | -| `tributo.exporting.models` — all public models | `beta` | Configuration, artifact, canonical Bundle reference, and exact publication result models | +| `tributo.exporting.models` — core storage values | `stable` | AliasConfig, artifact/producer/reference/validation values, execution/failure results, BundleResult, BundleRef, ResolvedArtifact, and PublishedBundle | +| `tributo.exporting.models` — export configuration, Hook and plugin values | `beta` | Exporter orchestration and Hook payloads retain independent contracts | | `tributo.exporting.protocols` — all protocols | `beta` | Exporter/Validator/SourceProvider contracts | -| `tributo.exporting.manifest` — `ExportManifest` | `beta` | Bundle manifest (schema v1) | -| `tributo.exporting.bundle_reader` — `BundleReader` | `beta` | Repository-routed Bundle consumption with exact manifest-byte and artifact verification | +| `tributo.exporting.manifest` — schema-v1 models and registry | `stable` | Source info, typed signatures, execution records, ExportManifest, and schema reader registration | +| `tributo.exporting.manifest` — `ExportManifestV2` and `compute_bundle_digest` | `beta` | Explainability extension and compatibility digest helper | +| `tributo.exporting.bundle_reader` — `BundleReader` | `stable` | Repository-routed Bundle consumption with exact manifest-byte and artifact verification | | `tributo.exporting.planner` | `beta` | Export plan builder | | `tributo.exporting.executor` | `beta` | Export executor | -| `tributo.exporting.publisher` | `beta` | Bundle publisher | +| `tributo.exporting.publisher` | `stable` | Local/file/S3 core publication; the optional Explainability parameter remains Alpha | | `tributo.exporting.validators` | `beta` | Artifact validator runner | | `tributo.exporting.registries` | `beta` | Exporter/validator registries | | `tributo.exporting.options` | `beta` | Compatibility re-exports; schemas are owned by integration exporters | @@ -173,10 +175,33 @@ source identity reject credential material rather than persist it. | `tributo.exporting.hooks` | `beta` | Adapter and committed-artifact access contracts | | `tributo.exporting.dispatch` | `beta` | Inline Hook dispatch policy | | `tributo.exporting.capabilities` | `beta` | Exporter/Flavor-derived capability declarations | -| `tributo.exporting.repository` | `beta` | Bundle repository and alias store ports | +| `tributo.exporting.repository` | `stable` | Bundle repository/alias ports and their value objects; internal routing remains DeveloperAPI | | `tributo.exporting.runtime` | `beta` | Bundle model runtime and Flavor protocol | | `tributo.exporting.conftest` | `beta` | Public plugin conformance test kit | +The Stable storage model allowlist is `AliasConfig`, `ArtifactFile`, +`ProducerInfo`, `ArtifactRef`, `LogicalArtifact`, `ResolvedArtifact`, +`FailureInfo`, `NodeResult`, `ExportExecutionResult`, `BundleResult`, `BundleRef`, +`PublishedBundle`, and `ValidationResult`. The Stable manifest allowlist is +`ManifestSourceInfo`, `SignatureField`, `ManifestSignature`, +`ManifestExecutionNode`, `ManifestExecution`, `ExportManifest`, and +`ManifestSchemaRegistry`. `BundleCommitBusyError` and `AliasConflict` are Stable +storage errors. The high-level `export`, `ExportSpec`, and `load_bundle` +compatibility facade remain Beta. + +The Stable promise covers the core storage operation and fields. Passing Alpha +`ExplainabilityConfig` opts into its independent contract, and +`ExportManifestV2` remains Beta. `BundleResult.hook_receipts` keeps the Beta +`HookReceipt` payload; Hook delivery and orchestration do not become Stable. +Developer router/assembler injection remains an advanced extension with its +existing constructor parameters. No signature or extension payload is removed. + +`ResolvedArtifact` paths are valid inside their materialization context. +`PublishedBundle.local_dir_ephemeral` distinguishes transient S3 staging from +persistent local publication. Ephemeral paths belong to the staging owner; +BundleExportService guarantees their callback window. Consumers persist +`BundleRef` or `BundleResult.canonical_uri` instead of transient local paths. + ### Explainability (tributo.explainability.*) | Module | Level | Notes | @@ -288,7 +313,7 @@ source identity reject credential material rather than persist it. | Module | Level | Notes | |--------|-------|-------| -| `tributo._common.storage_profiles` | `beta` | Storage profile resolution | +| `tributo._common.storage_profiles` | `stable` | S3 profile values, named/default resolution, and credential-safe diagnostics | | `tributo._common.dependencies` | `beta` | Unified dependency probing layer | | `tributo._common` | `developer` | Internal shared package | | All other `tributo._common.*` | `developer` | Internal shared utilities | diff --git a/docs/reference/api/model-lifecycle.md b/docs/reference/api/model-lifecycle.md index 8cade0db..748a523b 100644 --- a/docs/reference/api/model-lifecycle.md +++ b/docs/reference/api/model-lifecycle.md @@ -233,6 +233,14 @@ documentation for every public stability tier. :no-members: ``` +```{autoclass} tributo.exporting.models.PublishedBundle +:no-members: +``` + +```{autoclass} tributo.exporting.models.ResolvedArtifact +:no-members: +``` + ```{autoclass} tributo.exporting.models.SupportRequest :no-members: ``` @@ -331,6 +339,10 @@ documentation for every public stability tier. ## `tributo.exporting.repository` +```{autoclass} tributo.exporting.repository.AliasUpdate +:no-members: +``` + ```{autoclass} tributo.exporting.repository.BundleAliasStore :no-members: ``` @@ -343,6 +355,14 @@ documentation for every public stability tier. :no-members: ``` +```{autoclass} tributo.exporting.repository.RepositoryCommit +:no-members: +``` + +```{autoclass} tributo.exporting.repository.StagedBundle +:no-members: +``` + ## `tributo.exporting.runtime` diff --git a/docs/reference/support-matrix.md b/docs/reference/support-matrix.md index c27aebac..ffc671c6 100644 --- a/docs/reference/support-matrix.md +++ b/docs/reference/support-matrix.md @@ -103,8 +103,9 @@ matrix are generated from the same Registry projection. | Capability | Status | Boundary | | --- | --- | --- | -| Local and `file://` bundle publication | Beta | Manifest and digest validation | -| S3 bundle publication | Beta | Manifest-last and alias compare-and-set | +| Local and `file://` bundle publication | Stable | Core Publisher/repository contracts, immutable identity, manifest and digest validation; Explainability and Hook extensions retain their own levels | +| S3 bundle publication | Stable | Core Publisher/repository contracts, manifest-last, leases, idempotency, and alias compare-and-set | +| Bundle manifest/artifact reading and integrity | Stable | BundleReader, schema-v1 fields, exact committed bytes, digest/identity checks, bounded materialization, and context lifetime; extension payload semantics are independent | | HDFS bundle publication | Not implemented | Storage backend extension | | Ray Data batch inference | Beta | Actor-based model reuse | | Batch output to local/S3 Parquet | Implemented | Database sinks are separate | diff --git a/src/tributo/_common/storage_profiles.py b/src/tributo/_common/storage_profiles.py index da109da3..b5062e02 100644 --- a/src/tributo/_common/storage_profiles.py +++ b/src/tributo/_common/storage_profiles.py @@ -15,10 +15,15 @@ from tributo.util.annotations import PublicAPI -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") @dataclass(frozen=True) class StorageProfile: - """Resolved S3-compatible storage parameters.""" + """Resolved S3-compatible storage parameters. + + Credential fields are trusted runtime data and excluded from repr. + Profile resolution and the existing boto3 keyword projection are stable; + no credentials are persisted into bundle manifests by this value object. + """ endpoint: str | None = field(default=None, repr=False) region: str | None = None @@ -54,7 +59,7 @@ def to_boto3_kwargs(self) -> dict[str, Any]: return kwargs -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class StorageProfileResolver: """Resolves a profile *name* to connection parameters. @@ -64,8 +69,8 @@ class StorageProfileResolver: 2. ``boto3`` standard credential chain (env vars, ``~/.aws/credentials``, instance metadata). - Plugins may register additional resolvers via entry-point - ``tributo.storage_resolvers``. + Consumer constructors may receive a custom resolver. The built-in resolver + does not discover an additional resolver entry-point group. """ def __init__(self) -> None: diff --git a/src/tributo/exceptions.py b/src/tributo/exceptions.py index 69ceeeda..738dd227 100644 --- a/src/tributo/exceptions.py +++ b/src/tributo/exceptions.py @@ -151,7 +151,7 @@ def __init__(self, message: str, execution_result: Any = None) -> None: self.execution_result = execution_result -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class BundleCommitBusyError(TributoError): """A bundle commit is temporarily blocked by another active writer. @@ -161,7 +161,7 @@ class BundleCommitBusyError(TributoError): retryable = True -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class AliasConflict(TributoError): """Alias CAS update failed — concurrent modification detected.""" diff --git a/src/tributo/exporting/bundle_reader.py b/src/tributo/exporting/bundle_reader.py index b94265b7..6240d371 100644 --- a/src/tributo/exporting/bundle_reader.py +++ b/src/tributo/exporting/bundle_reader.py @@ -21,9 +21,16 @@ from tributo.util.annotations import PublicAPI -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class BundleReader: - """Read and verify bundles through storage-independent repository ports.""" + """Read and verify bundles through storage-independent repository ports. + + Stable consumption covers local/file/S3 locations, exact manifest bytes, + immutable references, resource limits, and verified artifact contexts. + Manifest v2 remains readable, but its explainability payload keeps its + existing experimental contract. Custom schema readers and developer router + injection do not turn their extensions into stable first-party capabilities. + """ def __init__( self, diff --git a/src/tributo/exporting/manifest.py b/src/tributo/exporting/manifest.py index b829d55d..21213c49 100644 --- a/src/tributo/exporting/manifest.py +++ b/src/tributo/exporting/manifest.py @@ -38,7 +38,7 @@ # ── Manifest models ──────────────────────────────────────────────────────────── -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ManifestSourceInfo(BaseModel): """Stable source identification — no temp paths, credentials, or samples.""" @@ -52,7 +52,7 @@ class ManifestSourceInfo(BaseModel): task_type: str | None = None -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class SignatureField(BaseModel): """A single input/output field with its data type and shape. @@ -87,7 +87,7 @@ def _check_shape(cls, v: tuple[Any, ...]) -> tuple[Any, ...]: return v -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ManifestSignature(BaseModel): """Input / output signature recorded in the manifest. @@ -176,7 +176,7 @@ def _check_dynamic_axes_consistency( ) -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ManifestExecutionNode(BaseModel): """A single DAG node as recorded in the manifest. @@ -196,7 +196,7 @@ class ManifestExecutionNode(BaseModel): duration_ms: int = 0 -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ManifestExecution(BaseModel): """Execution summary recorded in the manifest.""" @@ -206,7 +206,7 @@ class ManifestExecution(BaseModel): nodes: tuple[ManifestExecutionNode, ...] = () -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ExportManifest(BaseModel): """Canonical manifest for a published model bundle (schema v1). @@ -291,7 +291,7 @@ def _rebuild_manifest_v2() -> None: # ── Schema registry ──────────────────────────────────────────────────────────── -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ManifestSchemaRegistry: """Versioned manifest reader registry. diff --git a/src/tributo/exporting/models.py b/src/tributo/exporting/models.py index f6fe0946..65b0f593 100644 --- a/src/tributo/exporting/models.py +++ b/src/tributo/exporting/models.py @@ -25,7 +25,7 @@ ) from tributo.exporting.formats import validate_format_id -from tributo.util.annotations import DeveloperAPI, PublicAPI +from tributo.util.annotations import PublicAPI if TYPE_CHECKING: from tributo.exporting.manifest import ManifestSignature @@ -110,7 +110,7 @@ class HookReceipt(BaseModel): external_references: dict[str, str] = Field(default_factory=dict) -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class AliasConfig(BaseModel): """Optional stable alias for a published bundle.""" @@ -475,7 +475,7 @@ def to_manifest_signatures( # ── Artifact models ────────────────────────────────────────────────────────── -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ArtifactFile(BaseModel): """A single file within a logical artifact.""" @@ -511,7 +511,7 @@ def _check_path(cls, v: str) -> str: return _validate_posix_relative(v, "relative_path") -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ProducerInfo(BaseModel): """Exporter identity and version information.""" @@ -525,7 +525,7 @@ class ProducerInfo(BaseModel): effective_options_digest: str = Field(default="", min_length=0) -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ArtifactRef(BaseModel): """A reference to a published artifact by node_id and tree digest.""" @@ -581,7 +581,7 @@ def _check_entrypoint(self) -> ArtifactDraft: return self -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class LogicalArtifact(BaseModel): """A verified, hash-materialized artifact ready for publishing.""" @@ -638,7 +638,7 @@ def compute_tree_digest(cls, files: tuple[ArtifactFile, ...]) -> str: return hashlib.sha256(payload).hexdigest() -@DeveloperAPI +@PublicAPI(stability="stable") class ResolvedArtifact: """In-process view of an artifact with a local root directory. @@ -661,7 +661,7 @@ def path_for(self, relative_path: str) -> Path: # ── Execution results ──────────────────────────────────────────────────────── -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class FailureInfo(BaseModel): """Structured, sanitised failure detail — no tracebacks or credentials.""" @@ -675,7 +675,7 @@ class FailureInfo(BaseModel): retryable: bool = False -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class NodeResult(BaseModel): """Result of a single DAG node execution.""" @@ -694,7 +694,7 @@ class NodeResult(BaseModel): duration_ms: int = Field(default=0, ge=0) -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ExportExecutionResult(BaseModel): """Complete result of an export DAG execution (before publishing).""" @@ -716,9 +716,14 @@ def succeeded_artifacts(self) -> dict[str, ArtifactRef]: } -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class BundleResult(BaseModel): - """Immutable result returned to the caller after a successful publish.""" + """Immutable result returned to the caller after a successful publish. + + Core storage identity, artifact, role, and alias fields are stable. + ``hook_receipts`` retains the beta HookReceipt payload contract; storing a + receipt does not promise a hook's external delivery behavior. + """ model_config = ConfigDict(frozen=True, extra="forbid") @@ -740,7 +745,7 @@ class BundleResult(BaseModel): hook_receipts: tuple[HookReceipt, ...] = () -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class BundleRef(BaseModel): """Immutable reference to a committed bundle. @@ -756,11 +761,14 @@ class BundleRef(BaseModel): manifest_sha256: str -@DeveloperAPI +@PublicAPI(stability="stable") class PublishedBundle: """Transient handle returned within BundleExportService context. - ``local_bundle_dir`` is only valid during the callback window. + ``local_dir_ephemeral`` distinguishes caller-owned staging (S3) from a + persistent local publication directory. Ephemeral files are valid only while + the staging owner keeps them alive; BundleExportService provides that window + to its callback. Persist BundleRef or the result's canonical URI instead. """ def __init__( @@ -779,7 +787,7 @@ def __init__( # ── Validation models ──────────────────────────────────────────────────────── -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class ValidationResult(BaseModel): """Result of a single validator run.""" diff --git a/src/tributo/exporting/publisher.py b/src/tributo/exporting/publisher.py index 81b741cc..7979eef1 100644 --- a/src/tributo/exporting/publisher.py +++ b/src/tributo/exporting/publisher.py @@ -30,13 +30,19 @@ logger = logging.getLogger(__name__) -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class Publisher: """Compatibility facade over assembly and repository ports. Storage-specific atomicity, idempotency, and alias compare-and-set behavior belongs to repository adapters. This facade keeps the existing public method stable while composing those domain contracts. + + Stable publication covers local/file/S3 immutable bundles and alias results. + The ``explainability`` argument retains its alpha contract and produces the + beta Manifest v2 extension. Hook delivery and exporter orchestration are + separate contracts. Advanced assembler/router injection remains a developer + extension; existing constructor arguments are retained. """ def __init__( @@ -47,9 +53,9 @@ def __init__( assembler: BundleAssembler | None = None, repository_router: BundleRepositoryRouter | None = None, ) -> None: - # Compatibility-window argument. Manifest parsing moved to - # BundleReader; keep accepting the old Publisher constructor surface - # until the documented minor-version window closes. + # Compatibility argument. Manifest parsing moved to BundleReader. + # Keep this constructor surface in the current major version; + # removal must follow the Stable API policy and migration guide. del manifest_registry self._assembler = assembler or BundleAssembler() self._repository_router = repository_router or build_default_repository_router( diff --git a/src/tributo/exporting/repository.py b/src/tributo/exporting/repository.py index efd41037..e4ed8f2e 100644 --- a/src/tributo/exporting/repository.py +++ b/src/tributo/exporting/repository.py @@ -19,7 +19,7 @@ from tributo.util.annotations import DeveloperAPI, PublicAPI -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") @dataclass(frozen=True) class ReaderResourceLimits: """Resource limits enforced before repository materialization.""" @@ -30,7 +30,7 @@ class ReaderResourceLimits: max_total_bytes: int = 50 * 1024 * 1024 * 1024 -@DeveloperAPI +@PublicAPI(stability="stable") @dataclass(frozen=True) class StagedBundle: """Storage-neutral immutable bundle assembled before commit.""" @@ -43,7 +43,7 @@ class StagedBundle: staging_root: Path -@DeveloperAPI +@PublicAPI(stability="stable") @dataclass(frozen=True) class RepositoryCommit: """Repository result after an immutable bundle commit.""" @@ -55,7 +55,7 @@ class RepositoryCommit: local_dir_ephemeral: bool -@DeveloperAPI +@PublicAPI(stability="stable") @dataclass(frozen=True) class AliasUpdate: """Storage-level alias update result.""" @@ -66,7 +66,7 @@ class AliasUpdate: @runtime_checkable -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class BundleRepository(Protocol): """Commit, read, and materialize immutable bundles for URI schemes. @@ -118,7 +118,7 @@ def materialize_artifact( @runtime_checkable -@PublicAPI(stability="beta") +@PublicAPI(stability="stable") class BundleAliasStore(Protocol): """Resolve and compare-and-set aliases in a storage namespace.""" diff --git a/tests/test_stability_inventory.py b/tests/test_stability_inventory.py index eab96132..8e96d8fc 100644 --- a/tests/test_stability_inventory.py +++ b/tests/test_stability_inventory.py @@ -114,10 +114,10 @@ "tributo.exporting.models": "beta", "tributo.exporting.protocols": "beta", "tributo.exporting.manifest": "beta", - "tributo.exporting.bundle_reader": "beta", + "tributo.exporting.bundle_reader": "stable", "tributo.exporting.planner": "beta", "tributo.exporting.executor": "beta", - "tributo.exporting.publisher": "beta", + "tributo.exporting.publisher": "stable", "tributo.exporting.validators": "beta", "tributo.exporting.registries": "beta", "tributo.exporting.options": "beta", @@ -127,7 +127,7 @@ "tributo.exporting.capabilities": "beta", "tributo.exporting.events": "beta", "tributo.exporting.dispatch": "beta", - "tributo.exporting.repository": "beta", + "tributo.exporting.repository": "stable", "tributo.exporting.runtime": "beta", "tributo.exporting.conftest": "beta", # Explainability — alpha @@ -200,7 +200,7 @@ "tributo.plugin": "beta", # Common — developer "tributo._common": "developer", - "tributo._common.storage_profiles": "beta", + "tributo._common.storage_profiles": "stable", "tributo._common.dependencies": "beta", } @@ -218,6 +218,26 @@ "tributo.data.source_config.ProviderSourceConfig": "stable", "tributo.data.source_config.source_projection": "stable", "tributo.data.source_config.apply_source_projection": "stable", + "tributo.exporting.models.AliasConfig": "stable", + "tributo.exporting.models.ArtifactFile": "stable", + "tributo.exporting.models.ProducerInfo": "stable", + "tributo.exporting.models.ArtifactRef": "stable", + "tributo.exporting.models.LogicalArtifact": "stable", + "tributo.exporting.models.ResolvedArtifact": "stable", + "tributo.exporting.models.FailureInfo": "stable", + "tributo.exporting.models.NodeResult": "stable", + "tributo.exporting.models.ExportExecutionResult": "stable", + "tributo.exporting.models.BundleResult": "stable", + "tributo.exporting.models.BundleRef": "stable", + "tributo.exporting.models.PublishedBundle": "stable", + "tributo.exporting.models.ValidationResult": "stable", + "tributo.exporting.manifest.ManifestSourceInfo": "stable", + "tributo.exporting.manifest.SignatureField": "stable", + "tributo.exporting.manifest.ManifestSignature": "stable", + "tributo.exporting.manifest.ManifestExecutionNode": "stable", + "tributo.exporting.manifest.ManifestExecution": "stable", + "tributo.exporting.manifest.ExportManifest": "stable", + "tributo.exporting.manifest.ManifestSchemaRegistry": "stable", "tributo.plugin.validate_distributed_algorithm_descriptor": "alpha", "tributo.config.AlgorithmExecutionConfig": "alpha", "tributo.config.AlgorithmInputConfig": "alpha", @@ -225,8 +245,8 @@ "tributo.config.LocalRayRuntimeConfig": "alpha", "tributo.exceptions.ResultMaterializationError": "alpha", "tributo.exceptions.BundleExportError": "beta", - "tributo.exceptions.BundleCommitBusyError": "beta", - "tributo.exceptions.AliasConflict": "beta", + "tributo.exceptions.BundleCommitBusyError": "stable", + "tributo.exceptions.AliasConflict": "stable", "tributo.exceptions.UnsupportedArtifactFormat": "beta", "tributo.exceptions.PostPublishCallbackError": "beta", "tributo.exceptions.PluginLoadIssue": "beta", diff --git a/tests/training/exporters/test_storage_contract_scope.py b/tests/training/exporters/test_storage_contract_scope.py new file mode 100644 index 00000000..93a07bba --- /dev/null +++ b/tests/training/exporters/test_storage_contract_scope.py @@ -0,0 +1,88 @@ +"""Stable storage metadata and explicit experimental extension boundaries.""" + +from __future__ import annotations + +import json + +import pytest + +from tributo._common.storage_profiles import StorageProfileResolver +from tributo.exceptions import ( + AliasConflict, + BundleCommitBusyError, + JobConfigurationError, +) +from tributo.explainability.contracts import ( + ExplainabilityConfig, + ExplainabilityDescriptor, +) +from tributo.exporting import export, load_bundle +from tributo.exporting.bundle_reader import BundleReader +from tributo.exporting.manifest import ExportManifest, ExportManifestV2 +from tributo.exporting.models import ( + BundleRef, + BundleResult, + HookReceipt, + PublishedBundle, +) +from tributo.exporting.publisher import Publisher +from tributo.exporting.repository import BundleAliasStore, BundleRepository +from tributo.util.annotations import get_stability + + +def test_core_storage_metadata_does_not_promote_extensions() -> None: + for obj in ( + BundleReader, + Publisher, + BundleRepository, + BundleAliasStore, + BundleRef, + BundleResult, + PublishedBundle, + ExportManifest, + BundleCommitBusyError, + AliasConflict, + ): + assert get_stability(obj) == "stable" + for beta_obj in (ExportManifestV2, HookReceipt, export, load_bundle): + assert get_stability(beta_obj) == "beta" + for alpha_obj in (ExplainabilityConfig, ExplainabilityDescriptor): + assert get_stability(alpha_obj) == "alpha" + + +def test_named_profile_preserves_credential_safety( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv( + "TRIBUTO_STORAGE_PROFILE_CONTRACT", + json.dumps( + { + "endpoint": "http://minio.example:9000", + "region": "us-east-1", + "path_style": True, + "access_key_id": "fixture-key", + "secret_access_key": "fixture-secret", + } + ), + ) + profile = StorageProfileResolver().resolve("contract") + assert profile.endpoint == "http://minio.example:9000" + assert profile.path_style + assert profile.access_key_id == "fixture-key" + assert profile.secret_access_key == "fixture-secret" + assert "fixture-key" not in repr(profile) + assert "fixture-secret" not in repr(profile) + assert "minio.example" not in repr(profile) + + +def test_invalid_profile_diagnostic_does_not_echo_input( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv( + "TRIBUTO_STORAGE_PROFILE_CONTRACT", + json.dumps({"unknown": "fixture-secret"}), + ) + with pytest.raises(JobConfigurationError) as error: + StorageProfileResolver().resolve("contract") + assert "invalid storage profile fields" in str(error.value) + assert "fixture-secret" not in str(error.value)