Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
03f6992
Record a snapshot manifest next to every graph index
Vedaant-J Oct 20, 2026
a778565
Address snapshot manifest review feedback
Vedaant-J Oct 20, 2026
55d1ee5
Match the backfill diff when no build commit is recorded
Vedaant-J Oct 20, 2026
c8faa43
Record each source table's full location in the snapshot manifest
Vedaant-J Oct 21, 2026
8fc3b37
Drop graph_id and data_version from the snapshot manifest; keep the d…
Vedaant-J Oct 21, 2026
e514912
Tighten manifest schema checks and scheduler context
Vedaant-J Oct 21, 2026
83c4756
Drop snapshot-manifest tests of private helpers
Vedaant-J Oct 21, 2026
92801ea
Trim snapshot manifest PR to essentials
Vedaant-J Oct 21, 2026
acb2810
Reuse existing helpers in the snapshot manifest
Vedaant-J Oct 21, 2026
11ec73f
Record DiskGraph's version and build commit in manifests
Vedaant-J Oct 21, 2026
3643180
Type source versions and allow location-free snapshot manifests
Vedaant-J Oct 21, 2026
677d15a
Pick the source-location lookup by connector type
Vedaant-J Oct 21, 2026
e1d24f5
Record Custom SQL text by default; one record_sources switch
Vedaant-J Oct 21, 2026
3fae7ca
Stamp the engine build commit into wheels; type directory versions
Vedaant-J Oct 21, 2026
5656515
Carry the record_sources choice on GraphSnapshot
Vedaant-J Oct 22, 2026
72308cf
Record Custom SQL's namespace and normalized fingerprint
Vedaant-J Oct 22, 2026
7b6ad79
Make the snapshot manifest a frozen dataclass
Vedaant-J Oct 22, 2026
27996c2
Add the MLOps switch; stub parents; drop scheduler context from manif…
Vedaant-J Oct 24, 2026
9d3ccf7
Record training-table lineage across export and reload
Vedaant-J Oct 20, 2026
5f0178f
Digest exported parts at the source for training-table lineage
Vedaant-J Oct 20, 2026
f1b261a
Record a custom table's source instead of re-reading its export
Vedaant-J Oct 21, 2026
e25b688
Record a custom table's source in the snapshot manifest's shape
Vedaant-J Oct 21, 2026
8de3f80
Keep sidecar writes best-effort; version custom tables from their read
Vedaant-J Oct 21, 2026
ea6fe84
Trim training-table lineage to essentials
Vedaant-J Oct 21, 2026
a494a09
Read row counts and source mode from DiskGraph's manifest
Vedaant-J Oct 21, 2026
fc41202
Record a custom table's source as the snapshot's manifest does
Vedaant-J Oct 21, 2026
d9743a8
Export a training table even when its manifest sidecar fails
Vedaant-J Oct 21, 2026
20fe503
Follow the snapshot's record_sources choice
Vedaant-J Oct 21, 2026
d88a447
Expect the listing kind for a reloaded export directory
Vedaant-J Oct 21, 2026
918d0f2
Record a custom table's source only when the snapshot did
Vedaant-J Oct 22, 2026
27fc36d
Make the training-table manifest a frozen dataclass
Vedaant-J Oct 22, 2026
7858ace
Give training tables a deterministic id, embedded snapshot, root store
Vedaant-J Oct 24, 2026
732f4c3
Export TrainingTableManifest
Vedaant-J Oct 24, 2026
cca0d00
Record bundle_id and fit provenance in every model bundle
Vedaant-J Oct 20, 2026
1b67699
Address bundle provenance review feedback
Vedaant-J Oct 20, 2026
d15d97e
Read Trainer.bundle_id defensively
Vedaant-J Oct 20, 2026
ffbf206
Record the published model's fit mode in provenance.json
Vedaant-J Oct 20, 2026
2a6fea3
Mirror the training table's source in bundle provenance
Vedaant-J Oct 21, 2026
68e72b9
Trim provenance.json to facts no other bundle file holds
Vedaant-J Oct 21, 2026
c5fe930
Record fit_mode and source versions in provenance; expose it on Trainer
Vedaant-J Oct 21, 2026
d3de46a
Trim bundle provenance to essentials
Vedaant-J Oct 21, 2026
efaeced
Reuse shared helpers for bundle provenance
Vedaant-J Oct 21, 2026
c728893
Compute a fresh bundle's id from its publication manifest
Vedaant-J Oct 21, 2026
58b80d3
Copy a custom table's full source entry into provenance
Vedaant-J Oct 21, 2026
f70ee4d
Store the bundle id in provenance.json
Vedaant-J Oct 21, 2026
4217144
Make bundle provenance a frozen dataclass
Vedaant-J Oct 22, 2026
1320948
Embed the training table in a self-contained model manifest; store mo…
Vedaant-J Oct 24, 2026
456b97d
Store the model under the root after the plain export
Vedaant-J Oct 24, 2026
231a309
Add GraphSnapshot export and load through object storage
Vedaant-J Oct 20, 2026
3fd23a1
Ship the object-store sink in the public connector image
Vedaant-J Oct 20, 2026
faabba5
Report source changes since a snapshot and warn on stale loads
Vedaant-J Oct 20, 2026
e315517
Match source changes on the recorded connector and location
Vedaant-J Oct 21, 2026
2c75c50
Keep an existing export when a rebuilt snapshot is re-exported
Vedaant-J Oct 21, 2026
2b40f15
Pass allow_delayed_open to S3 only when pyarrow supports it
Vedaant-J Oct 21, 2026
fa780f0
Trim comments and implementation-detail tests
Vedaant-J Oct 21, 2026
6cf5f82
Trim snapshot export PR to essentials
Vedaant-J Oct 21, 2026
5f8239a
Move snapshot export checksums into DiskGraph and reuse its manifest
Vedaant-J Oct 21, 2026
66fb6d2
Check loaded snapshots with what DiskGraph already stamps
Vedaant-J Oct 21, 2026
6bc6d6c
Derive GraphSnapshot.graph_key from the snapshot id
Vedaant-J Oct 21, 2026
51f5e8c
Make snapshot export atomic and pass the snapshot manifest through
Vedaant-J Oct 21, 2026
e910bc2
Drop the S3 upload tuning from snapshot export
Vedaant-J Oct 21, 2026
b1924ef
Repair files a concurrent export wiped before committing
Vedaant-J Oct 21, 2026
6839eea
Compare directory listings in source_changes
Vedaant-J Oct 21, 2026
1e2b14c
Drop the connector-image allowlist entry for the removed sink test
Vedaant-J Oct 21, 2026
cc48acd
Raise ConcurrentExportError when another export keeps replacing the d…
Vedaant-J Oct 22, 2026
318c18b
Read snapshot manifests as dataclasses in export and source_changes
Vedaant-J Oct 22, 2026
46465ff
Export snapshots through a config to any file store; store in MLOps s…
Vedaant-J Oct 24, 2026
68152b0
Add PredictiveQuery.from_snapshot for graph-free scoring
Vedaant-J Oct 20, 2026
5256d2c
Expose Trainer.training_query and keep the graph-bound snapshot check
Vedaant-J Oct 21, 2026
81fb21c
Trim comments and implementation-detail tests
Vedaant-J Oct 21, 2026
4ffea50
Read the native graph and graph id from GraphSnapshot
Vedaant-J Oct 21, 2026
76da3dc
Add Trainer.predict_snapshot for one-call graph-free scoring
Vedaant-J Oct 21, 2026
aab9d02
Test graph-free scoring of an exported snapshot end to end
Vedaant-J Oct 21, 2026
32e9cdd
Warn when predict_snapshot scores another graph's snapshot
Vedaant-J Oct 22, 2026
c5b4bc8
One predict path: predict(snapshot, ...); results predict too
Vedaant-J Oct 24, 2026
4fd38b9
Hide the torch-scatter hint across all of predict()
Vedaant-J Oct 24, 2026
803cd82
Compare reloaded-model scores within float tolerance
Vedaant-J Oct 24, 2026
7cdc1ef
Report fit progress events to callbacks
Vedaant-J Oct 24, 2026
d97b285
Record Trainer.fit in MLflow through MLOps tracking
Vedaant-J Oct 24, 2026
ef5ed4b
Record Trainer.fit in Snowflake Experiment Tracking
Vedaant-J Oct 24, 2026
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
4 changes: 3 additions & 1 deletion 3rdparty/notices/inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@
"rust:cpp-demangle@0.4.5",
"rust:cpufeatures@0.2.17",
"rust:cpufeatures@0.3.0",
"rust:crc32fast@1.5.0",
"rust:crossbeam-deque@0.8.6",
"rust:crossbeam-epoch@0.9.21",
"rust:crossbeam-utils@0.8.21",
Expand Down Expand Up @@ -3642,7 +3643,8 @@
},
{
"artifact_ids": [
"diskgraph-extension"
"diskgraph-extension",
"diskgraph-ingest"
],
"copyright": [
"Copyright (c) 2018 Sam Rijs, Alex Crichton and contributors"
Expand Down
2 changes: 1 addition & 1 deletion THIRD-PARTY.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/diskgraph/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/diskgraph/diskgraph-ingest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ epserde = { workspace = true }
memmap2 = "0.9"
libc = "0.2"
rayon = "1"
crc32fast = "1.5"
jemallocator = { version = "0.5", optional = true, features = ["profiling"] }
jemalloc-ctl = { version = "0.5", optional = true }
sysinfo = "0.30"
Expand Down
134 changes: 119 additions & 15 deletions packages/diskgraph/diskgraph-ingest/src/snapshot_mirror.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use anyhow::{Context, Result};
use object_store::path::Path as ObjectPath;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

use diskgraph_fs::{CloudTransfer, CloudTransferTicket};

Expand Down Expand Up @@ -488,21 +489,9 @@ pub(crate) fn validate_manifest_rel_path(rel: &str) -> Result<(), &'static str>
Ok(())
}

/// Verify the snapshot rooted at `output_dir` against its `_MANIFEST.json`.
/// Used by the consumer side after a Python-driven dataplane→local copy
/// completes, to catch lifecycle-mid-delete races and partial copies
/// before the snapshot is handed to a workload.
///
/// Pass on success; [`CorruptionError`] on any deviation from the
/// manifest contract. Errors are precise about what failed so the
/// caller's alerting carries actionable detail (which file is missing,
/// expected vs actual size, etc.).
pub fn verify_snapshot_integrity(output_dir: &std::path::Path) -> std::result::Result<(), CorruptionError> {
let success_path = output_dir.join(SUCCESS_MARKER_FILENAME);
if !success_path.exists() {
return Err(CorruptionError::MissingSuccess(success_path));
}

/// Read and validate `_MANIFEST.json` at `output_dir`: schema version and
/// every rel_path's confinement to the snapshot root.
fn read_manifest(output_dir: &std::path::Path) -> std::result::Result<Manifest, CorruptionError> {
let manifest_path = output_dir.join(MANIFEST_FILENAME);
let manifest_bytes = match std::fs::read(&manifest_path) {
Ok(b) => b,
Expand Down Expand Up @@ -548,6 +537,25 @@ pub fn verify_snapshot_integrity(output_dir: &std::path::Path) -> std::result::R
});
}
}
Ok(manifest)
}

/// Verify the snapshot rooted at `output_dir` against its `_MANIFEST.json`.
/// Used by the consumer side after a Python-driven dataplane→local copy
/// completes, to catch lifecycle-mid-delete races and partial copies
/// before the snapshot is handed to a workload.
///
/// Pass on success; [`CorruptionError`] on any deviation from the
/// manifest contract. Errors are precise about what failed so the
/// caller's alerting carries actionable detail (which file is missing,
/// expected vs actual size, etc.).
pub fn verify_snapshot_integrity(output_dir: &std::path::Path) -> std::result::Result<(), CorruptionError> {
let success_path = output_dir.join(SUCCESS_MARKER_FILENAME);
if !success_path.exists() {
return Err(CorruptionError::MissingSuccess(success_path));
}

let manifest = read_manifest(output_dir)?;

let mut missing: Vec<String> = Vec::new();
let mut size_mismatches: Vec<(String, u64, u64)> = Vec::new();
Expand Down Expand Up @@ -584,6 +592,71 @@ pub fn verify_snapshot_integrity(output_dir: &std::path::Path) -> std::result::R
Ok(())
}

/// CRC-32 of every `range_bytes` span of each file `_MANIFEST.json` lists,
/// and of `_MANIFEST.json` itself, keyed by rel_path (an empty file has one
/// zero entry). Lets a copy of the snapshot be checked for content as well
/// as size; the manifest format is unchanged.
pub fn snapshot_range_checksums(
output_dir: &std::path::Path,
range_bytes: u64,
) -> std::result::Result<BTreeMap<String, Vec<u32>>, CorruptionError> {
use rayon::prelude::*;
use std::os::unix::fs::FileExt;

const CHUNK: u64 = 1 << 20;
if range_bytes == 0 {
return Err(CorruptionError::Io {
path: output_dir.to_path_buf(),
detail: "range_bytes must be positive".into(),
});
}
let mut files = read_manifest(output_dir)?.files;
let manifest_size = std::fs::metadata(output_dir.join(MANIFEST_FILENAME))
.map(|meta| meta.len())
.unwrap_or(0);
files.push(ManifestEntry {
rel_path: MANIFEST_FILENAME.into(),
size_bytes: manifest_size,
});
let ranges: Vec<(usize, u64)> = files
.iter()
.enumerate()
.flat_map(|(index, entry)| {
let count = entry.size_bytes.div_ceil(range_bytes).max(1);
(0..count).map(move |range| (index, range))
})
.collect();
let crcs = ranges
.par_iter()
.map(|&(index, range)| {
let entry = &files[index];
let path = output_dir.join(&entry.rel_path);
let io = |e: std::io::Error| CorruptionError::Io {
path: path.clone(),
detail: e.to_string(),
};
let file = std::fs::File::open(&path).map_err(io)?;
let start = range * range_bytes;
let end = entry.size_bytes.min(start + range_bytes);
let mut hasher = crc32fast::Hasher::new();
let mut buffer = vec![0u8; CHUNK.min(end - start) as usize];
let mut offset = start;
while offset < end {
let length = CHUNK.min(end - offset) as usize;
file.read_exact_at(&mut buffer[..length], offset).map_err(io)?;
hasher.update(&buffer[..length]);
offset += length as u64;
}
Ok(hasher.finalize())
})
.collect::<std::result::Result<Vec<u32>, CorruptionError>>()?;
let mut out: BTreeMap<String, Vec<u32>> = BTreeMap::new();
for (&(index, _), crc) in ranges.iter().zip(crcs) {
out.entry(files[index].rel_path.clone()).or_default().push(crc);
}
Ok(out)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -933,4 +1006,35 @@ mod tests {
verify_snapshot_integrity(local_root.path())?;
Ok(())
}

#[test]
fn range_checksums_cover_every_listed_file_and_the_manifest() {
let tmp = tempfile::tempdir().unwrap();
let big: Vec<u8> = (0..10u32).flat_map(|i| i.to_le_bytes()).collect();
write_marker_files(tmp.path(), &[("a.bin", &big), ("nested/empty", b"")]);
write_manifest(
tmp.path(),
vec![
ManifestEntry {
rel_path: "a.bin".into(),
size_bytes: 40,
},
ManifestEntry {
rel_path: "nested/empty".into(),
size_bytes: 0,
},
],
);
let crcs = snapshot_range_checksums(tmp.path(), 16).unwrap();
let manifest = std::fs::read(tmp.path().join(MANIFEST_FILENAME)).unwrap();
assert_eq!(crcs["a.bin"], [&big[..16], &big[16..32], &big[32..]].map(crc32fast::hash));
assert_eq!(crcs["nested/empty"], vec![0]);
assert_eq!(crcs[MANIFEST_FILENAME], manifest.chunks(16).map(crc32fast::hash).collect::<Vec<_>>());

std::fs::write(tmp.path().join("a.bin"), &big[..20]).unwrap();
assert!(matches!(
snapshot_range_checksums(tmp.path(), 16).unwrap_err(),
CorruptionError::Io { .. }
));
}
}
29 changes: 29 additions & 0 deletions packages/diskgraph/diskgraph-pybind/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,37 @@ mod native_cpu_guard {
));
}

fn build_commit() -> String {
println!("cargo:rerun-if-env-changed=DISKGRAPH_BUILD_COMMIT");
if let Ok(commit) = std::env::var("DISKGRAPH_BUILD_COMMIT") {
return commit;
}
let git = |args: &[&str]| {
std::process::Command::new("git")
.args(args)
.output()
.ok()
.filter(|out| out.status.success())
.map(|out| String::from_utf8_lossy(&out.stdout).trim().to_owned())
.filter(|out| !out.is_empty())
};
for name in ["HEAD", "packed-refs"] {
if let Some(path) = git(&["rev-parse", "--git-path", name]) {
println!("cargo:rerun-if-changed={path}");
}
}
if let Some(head) = git(&["symbolic-ref", "-q", "HEAD"]) {
if let Some(path) = git(&["rev-parse", "--git-path", &head]) {
println!("cargo:rerun-if-changed={path}");
}
}
git(&["rev-parse", "--short=12", "HEAD"]).unwrap_or_else(|| "unknown".into())
}

fn main() {
pyo3_build_config::use_pyo3_cfgs();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-env=DISKGRAPH_BUILD_COMMIT={}", build_commit());

if std::env::var_os("CARGO_FEATURE_EXTENSION_MODULE").is_some() {
native_cpu_guard::reject_native_cpu_build("diskgraph Python extension");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# SPDX-License-Identifier: Apache-2.0

from ._c import (
__version__,
BUILD_COMMIT,
export_tt_artifacts,
convert_kumo_config,
ingest,
Expand Down Expand Up @@ -38,6 +40,7 @@
write_success_marker,
touch_success_marker,
verify_snapshot_integrity,
snapshot_range_checksums,
has_resumable_ingest,
validated_index_resume_progress,
compute_col_stats_for_parquet,
Expand Down Expand Up @@ -139,6 +142,7 @@ def __getattr__(name):
"write_success_marker",
"touch_success_marker",
"verify_snapshot_integrity",
"snapshot_range_checksums",
"has_resumable_ingest",
"validated_index_resume_progress",
"compute_col_stats_for_parquet",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,27 @@
def test_public_corruption_error_catches_snapshot_verifier(tmp_path):
with pytest.raises(diskgraph.DiskgraphCorruptionError):
diskgraph.verify_snapshot_integrity(str(tmp_path))


def test_build_identity_is_exported():
assert diskgraph.__version__
assert diskgraph.BUILD_COMMIT


def test_snapshot_range_checksums_match_zlib(tmp_path):
import json
import zlib

payload = bytes(range(256)) * 5
(tmp_path / "a.bin").write_bytes(payload)
(tmp_path / "_MANIFEST.json").write_text(json.dumps({
"schema_version": 1,
"files": [{"rel_path": "a.bin", "size_bytes": len(payload)}],
}))
crcs = diskgraph.snapshot_range_checksums(str(tmp_path), 512)
assert crcs["a.bin"] == [zlib.crc32(payload[i:i + 512])
for i in range(0, len(payload), 512)]
assert crcs["_MANIFEST.json"] == [
zlib.crc32((tmp_path / "_MANIFEST.json").read_bytes())]
with pytest.raises(diskgraph.DiskgraphCorruptionError):
diskgraph.snapshot_range_checksums(str(tmp_path / "missing"), 512)
18 changes: 18 additions & 0 deletions packages/diskgraph/diskgraph-pybind/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1553,6 +1553,21 @@ fn verify_snapshot_integrity(py: Python<'_>, output_dir: &str) -> PyResult<()> {
.map_err(|e| DiskgraphCorruptionError::new_err(e.to_string()))
}

/// CRC-32 of every `range_bytes` span of each file in the snapshot's
/// `_MANIFEST.json` and of the manifest itself, keyed by relative path.
#[pyfunction]
fn snapshot_range_checksums(
py: Python<'_>,
output_dir: &str,
range_bytes: u64,
) -> PyResult<std::collections::BTreeMap<String, Vec<u32>>> {
let output_dir = std::path::PathBuf::from(output_dir);
py.detach(|| {
diskgraph_ingest::snapshot_mirror::snapshot_range_checksums(&output_dir, range_bytes)
})
.map_err(|e| DiskgraphCorruptionError::new_err(e.to_string()))
}

#[pyfunction]
fn has_resumable_ingest(output_dir: &str) -> PyResult<bool> {
diskgraph_ingest::initial_resume_phase(std::path::Path::new(output_dir))
Expand Down Expand Up @@ -1593,6 +1608,8 @@ fn diskgraph_pybind(m: &Bound<'_, PyModule>) -> PyResult<()> {
"DiskgraphCorruptionError",
m.py().get_type::<DiskgraphCorruptionError>(),
)?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
m.add("BUILD_COMMIT", env!("DISKGRAPH_BUILD_COMMIT"))?;
m.add_function(wrap_pyfunction!(export_tt_artifacts, m)?)?;
m.add_function(wrap_pyfunction!(convert_kumo_config, m)?)?;
m.add_function(wrap_pyfunction!(ingest, m)?)?;
Expand All @@ -1606,6 +1623,7 @@ fn diskgraph_pybind(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(success_marker::write_success_marker, m)?)?;
m.add_function(wrap_pyfunction!(success_marker::touch_success_marker, m)?)?;
m.add_function(wrap_pyfunction!(verify_snapshot_integrity, m)?)?;
m.add_function(wrap_pyfunction!(snapshot_range_checksums, m)?)?;
m.add_function(wrap_pyfunction!(has_resumable_ingest, m)?)?;
m.add_function(wrap_pyfunction!(validated_index_resume_progress, m)?)?;
m.add_function(wrap_pyfunction!(compute_col_stats_for_parquet, m)?)?;
Expand Down
9 changes: 8 additions & 1 deletion packages/diskgraph/third_party/artifact-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,13 @@
"source_kind": "registry-or-git",
"version": "0.3.0"
},
{
"license": "MIT OR Apache-2.0",
"name": "crc32fast",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"source_kind": "registry-or-git",
"version": "1.5.0"
},
{
"license": "MIT OR Apache-2.0",
"name": "crossbeam-deque",
Expand Down Expand Up @@ -5202,6 +5209,6 @@
]
}
],
"cargo_lock_sha256": "297851eb08303a13a97f091c68cf3ce5ccac633ad3d7fe09724c1506e486cdaf",
"cargo_lock_sha256": "a1c319a5eb83c1a60f056c213932c12e385f0cff468da4db0021947f11b7aa07",
"schema": 1
}
10 changes: 7 additions & 3 deletions packages/kumo-bigquery/src/kumo_bigquery/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

from typing import Any

from kumo_core.sources.identity import epoch_second, hour_version
from kumo_core.sources.identity import (
SourceVersion,
epoch_second,
hour_version,
)

LOGICAL_VIEW = "VIEW"
UNDATED_VERSION = "-1"
Expand All @@ -23,8 +27,8 @@ def stamp(table: Any) -> str:
if is_logical_view(table):
return hour_version()
if table.modified is None:
return UNDATED_VERSION
return epoch_second(table.modified)
return SourceVersion(UNDATED_VERSION)
return SourceVersion(epoch_second(table.modified), "timestamp")


def data_version(client: Any, fqn: str) -> str:
Expand Down
5 changes: 5 additions & 0 deletions packages/kumo-bigquery/src/kumo_bigquery/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,11 @@ def _credentials(self, connector: SourceConnector) -> Credentials:
_require_credentials(credentials)
return credentials

def table_location(self, connector: SourceConnector,
table_name: str) -> str:
"""Fully qualified name of ``table_name`` in the connector's target."""
return _name(connector, table_name)

def _client(self, connector: SourceConnector) -> Any:
return _clients.query_client(
self._principal(connector), project=_billing_project(connector),
Expand Down
Loading
Loading