tensorfs 0.3.22__tar.gz → 0.3.24__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tensorfs might be problematic. Click here for more details.
- {tensorfs-0.3.22 → tensorfs-0.3.24}/Cargo.lock +2 -2
- {tensorfs-0.3.22 → tensorfs-0.3.24}/Cargo.toml +1 -1
- {tensorfs-0.3.22 → tensorfs-0.3.24}/PKG-INFO +1 -1
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/checkpoint.rs +5 -1
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/providers.rs +117 -8
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/source_artifact.rs +56 -2
- tensorfs-0.3.24/crates/tensorfs-core/tests/import_tree.rs +217 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-py/src/store.rs +32 -21
- {tensorfs-0.3.22 → tensorfs-0.3.24}/pyproject.toml +1 -1
- {tensorfs-0.3.22 → tensorfs-0.3.24}/python/tensorfs/_ext.pyi +4 -1
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/Cargo.toml +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/derived.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/ingest/source.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/storage.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/store.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/transport/http.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/transport/pull.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/transport/tests.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/python/tensorfs/__init__.py +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.22 → tensorfs-0.3.24}/python/tensorfs/py.typed +0 -0
|
@@ -392,7 +392,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
392
392
|
|
|
393
393
|
[[package]]
|
|
394
394
|
name = "tensorfs-core"
|
|
395
|
-
version = "0.3.
|
|
395
|
+
version = "0.3.24"
|
|
396
396
|
dependencies = [
|
|
397
397
|
"fs2",
|
|
398
398
|
"minicbor",
|
|
@@ -404,7 +404,7 @@ dependencies = [
|
|
|
404
404
|
|
|
405
405
|
[[package]]
|
|
406
406
|
name = "tensorfs-py"
|
|
407
|
-
version = "0.3.
|
|
407
|
+
version = "0.3.24"
|
|
408
408
|
dependencies = [
|
|
409
409
|
"pyo3",
|
|
410
410
|
"tensorfs-core",
|
|
@@ -477,7 +477,11 @@ pub fn walk_cozytensors(store: &Store, snap: &Manifest) -> Result<Walk> {
|
|
|
477
477
|
/// Repository completeness, checkout, retention, and accounting use this full walk; workers
|
|
478
478
|
/// use `walk_cozytensors` because an optional README or sample image is not a runtime input.
|
|
479
479
|
pub fn walk(store: &Store, snap: &Manifest) -> Result<Walk> {
|
|
480
|
-
let mut w =
|
|
480
|
+
let mut w = if snap.header().is_some() {
|
|
481
|
+
walk_cozytensors(store, snap)?
|
|
482
|
+
} else {
|
|
483
|
+
Walk::default()
|
|
484
|
+
};
|
|
481
485
|
|
|
482
486
|
// Anything listed that the header does not account for is a sibling asset — with two
|
|
483
487
|
// audit-driven tightenings (external review, folded in at tfs-003):
|
|
@@ -392,12 +392,18 @@ fn list_observed(
|
|
|
392
392
|
format!("member name {name:?} is outside the grammar"),
|
|
393
393
|
);
|
|
394
394
|
}
|
|
395
|
+
let file_id = json_u64(file, "id")
|
|
396
|
+
.filter(|id| *id > 0 && *id < (1u64 << 53))
|
|
397
|
+
.ok_or_else(|| Refusal {
|
|
398
|
+
code: Code::KEY_GRAMMAR,
|
|
399
|
+
detail: "a Civitai file requires a positive exact integer id".into(),
|
|
400
|
+
})?;
|
|
395
401
|
let sha256 = json_get(file, "hashes")
|
|
396
402
|
.and_then(|hashes| json_str(hashes, "SHA256"))
|
|
397
403
|
.and_then(digest64);
|
|
398
404
|
let url = match json_str(file, "downloadUrl") {
|
|
399
405
|
Some(explicit) => explicit.to_string(),
|
|
400
|
-
None => format!("{base}/api/download/models/{version}"),
|
|
406
|
+
None => format!("{base}/api/download/models/{version}?fileId={file_id}"),
|
|
401
407
|
};
|
|
402
408
|
// A Civitai version is a bag of files (weights, a VAE, a config); the
|
|
403
409
|
// one to take is the `primary` SafeTensors file, exactly as the Go
|
|
@@ -413,7 +419,9 @@ fn list_observed(
|
|
|
413
419
|
// sizeKB is kilobytes as a float — an approximation, not a length.
|
|
414
420
|
// The exact length is probed from the origin at pull time.
|
|
415
421
|
members.push(SourceMember {
|
|
416
|
-
|
|
422
|
+
// Display filenames may change; the provider file ID is the reviewed
|
|
423
|
+
// profile member identity, shared with Creator and retained source trees.
|
|
424
|
+
member: format!("civitai/files/{file_id}"),
|
|
417
425
|
length: None,
|
|
418
426
|
sha256,
|
|
419
427
|
url,
|
|
@@ -1040,6 +1048,14 @@ fn is_safetensors(member: &str) -> bool {
|
|
|
1040
1048
|
lower_ends_with(member, ".safetensors")
|
|
1041
1049
|
}
|
|
1042
1050
|
|
|
1051
|
+
/// Civitai carrier members have provider IDs rather than filename extensions.
|
|
1052
|
+
pub(crate) fn is_civitai_member(member: &str) -> bool {
|
|
1053
|
+
member.strip_prefix("civitai/files/").is_some_and(|id| {
|
|
1054
|
+
id.parse::<u64>()
|
|
1055
|
+
.is_ok_and(|value| value > 0 && value < (1u64 << 53) && value.to_string() == id)
|
|
1056
|
+
})
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1043
1059
|
/// Resolve `relative` against the directory of `member`, the way the Go resolver's
|
|
1044
1060
|
/// `path.Clean(path.Join(path.Dir(member), shard))` does. `None` when the reference walks
|
|
1045
1061
|
/// above the repository root — an index may not name its way out of its own tree.
|
|
@@ -1970,12 +1986,16 @@ mod tests {
|
|
|
1970
1986
|
/// so the exact length comes from a ranged probe through the same fence.
|
|
1971
1987
|
#[test]
|
|
1972
1988
|
fn resolve_takes_the_one_primary_civitai_safetensors() {
|
|
1973
|
-
let
|
|
1989
|
+
let header =
|
|
1990
|
+
br#"{"model.diffusion_model.weight":{"dtype":"F32","shape":[1],"data_offsets":[0,4]}}"#;
|
|
1991
|
+
let mut body = (header.len() as u64).to_le_bytes().to_vec();
|
|
1992
|
+
body.extend_from_slice(header);
|
|
1993
|
+
body.extend_from_slice(&1f32.to_le_bytes());
|
|
1974
1994
|
let object = ObjectRef::of(&body);
|
|
1975
1995
|
let listing = format!(
|
|
1976
1996
|
r#"{{"id":777,"files":[
|
|
1977
|
-
{{"name":"model.safetensors","primary":true,"sizeKB":35.15,"hashes":{{"SHA256":"{}"}},"metadata":{{"format":"SafeTensor"}}}},
|
|
1978
|
-
{{"name":"extra.safetensors","primary":false,"sizeKB":1.0,"hashes":{{"SHA256":"{}"}}}}
|
|
1997
|
+
{{"id":92696,"name":"model.safetensors","primary":true,"sizeKB":35.15,"hashes":{{"SHA256":"{}"}},"metadata":{{"format":"SafeTensor"}}}},
|
|
1998
|
+
{{"id":92697,"name":"extra.safetensors","primary":false,"sizeKB":1.0,"hashes":{{"SHA256":"{}"}}}}
|
|
1979
1999
|
]}}"#,
|
|
1980
2000
|
object.sha256.to_ascii_uppercase(),
|
|
1981
2001
|
"11".repeat(32)
|
|
@@ -1999,12 +2019,101 @@ mod tests {
|
|
|
1999
2019
|
};
|
|
2000
2020
|
let resolved = resolve(&uri, &endpoints, &Anonymous, Deadline::none()).unwrap();
|
|
2001
2021
|
assert_eq!(resolved.members.len(), 1);
|
|
2002
|
-
assert_eq!(resolved.members[0].member, "
|
|
2022
|
+
assert_eq!(resolved.members[0].member, "civitai/files/92696");
|
|
2003
2023
|
assert_eq!(resolved.members[0].object, object);
|
|
2004
2024
|
assert_eq!(resolved.members[0].provenance, Provenance::Declared);
|
|
2005
2025
|
assert!(resolved.members[0].carrier);
|
|
2006
2026
|
assert_eq!(resolved.canonical, "civitai://777");
|
|
2007
2027
|
handle.join().unwrap();
|
|
2028
|
+
|
|
2029
|
+
// The real source tree must keep the carrier visible to conversion despite its
|
|
2030
|
+
// lack of a filename extension, under the member the reviewed profile selects.
|
|
2031
|
+
let root = temporary("civitai-member");
|
|
2032
|
+
let store = Store::init(&root).unwrap();
|
|
2033
|
+
store
|
|
2034
|
+
.put_stream(&mut body.as_slice(), Some(&object), &Fault::default())
|
|
2035
|
+
.unwrap();
|
|
2036
|
+
let tree = content_manifest(&resolved).unwrap();
|
|
2037
|
+
let owner = ObjectRef::of(b"civitai-member-owner").id();
|
|
2038
|
+
crate::source_artifact::create(&store, &owner, &tree).unwrap();
|
|
2039
|
+
let (_, roster, landed) =
|
|
2040
|
+
crate::source_artifact::conversion_roster(&store, &owner).unwrap();
|
|
2041
|
+
assert_eq!(roster.len(), 1);
|
|
2042
|
+
assert_eq!(roster[0].member, "civitai/files/92696");
|
|
2043
|
+
assert_eq!(roster[0].header, body[..8 + header.len()]);
|
|
2044
|
+
assert_eq!(landed, vec!["civitai/files/92696"]);
|
|
2045
|
+
let registry = crate::ingest::fingerprint::FingerprintRegistry::parse(
|
|
2046
|
+
crate::ingest::source::BUILTIN_REGISTRY_BYTES,
|
|
2047
|
+
)
|
|
2048
|
+
.unwrap();
|
|
2049
|
+
let profile = registry
|
|
2050
|
+
.source_profiles
|
|
2051
|
+
.iter()
|
|
2052
|
+
.find(|profile| profile.name == "civitai/101055/128078/single-file-fp16")
|
|
2053
|
+
.unwrap();
|
|
2054
|
+
assert!(
|
|
2055
|
+
profile
|
|
2056
|
+
.components
|
|
2057
|
+
.iter()
|
|
2058
|
+
.all(|component| component.source_member.as_deref()
|
|
2059
|
+
== Some(roster[0].member.as_str()))
|
|
2060
|
+
);
|
|
2061
|
+
std::fs::remove_dir_all(root).unwrap();
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
#[test]
|
|
2065
|
+
fn civitai_refuses_missing_invalid_and_duplicate_file_ids() {
|
|
2066
|
+
for id in ["null", "0", "-1", "1.5", "9007199254740992", "\"92696\""] {
|
|
2067
|
+
let listing = format!(r#"{{"files":[{{"id":{id},"name":"model.safetensors"}}]}}"#);
|
|
2068
|
+
let (base, _, handle) =
|
|
2069
|
+
origin(1, move |_| ok_body(listing.as_bytes(), "application/json"));
|
|
2070
|
+
let endpoints = Endpoints {
|
|
2071
|
+
civitai: base,
|
|
2072
|
+
allow_local: true,
|
|
2073
|
+
..Endpoints::default()
|
|
2074
|
+
};
|
|
2075
|
+
let refusal = list(
|
|
2076
|
+
&SourceUri::Civitai { version: 777 },
|
|
2077
|
+
&endpoints,
|
|
2078
|
+
&Anonymous,
|
|
2079
|
+
Deadline::none(),
|
|
2080
|
+
)
|
|
2081
|
+
.unwrap_err();
|
|
2082
|
+
let expected = if id == "9007199254740992" {
|
|
2083
|
+
Code::NUMBER_RANGE // The shared JSON decoder refuses before provider parsing.
|
|
2084
|
+
} else {
|
|
2085
|
+
Code::KEY_GRAMMAR
|
|
2086
|
+
};
|
|
2087
|
+
assert_eq!(refusal.code, expected, "{id}");
|
|
2088
|
+
handle.join().unwrap();
|
|
2089
|
+
}
|
|
2090
|
+
for (listing, code) in [
|
|
2091
|
+
(
|
|
2092
|
+
r#"{"files":[{"name":"missing-id.safetensors"}]}"#,
|
|
2093
|
+
Code::KEY_GRAMMAR,
|
|
2094
|
+
),
|
|
2095
|
+
(
|
|
2096
|
+
r#"{"files":[{"id":92696,"name":"a.safetensors"},{"id":92696,"name":"b.safetensors"}]}"#,
|
|
2097
|
+
Code::DUPLICATE_KEY,
|
|
2098
|
+
),
|
|
2099
|
+
] {
|
|
2100
|
+
let (base, _, handle) =
|
|
2101
|
+
origin(1, move |_| ok_body(listing.as_bytes(), "application/json"));
|
|
2102
|
+
let endpoints = Endpoints {
|
|
2103
|
+
civitai: base,
|
|
2104
|
+
allow_local: true,
|
|
2105
|
+
..Endpoints::default()
|
|
2106
|
+
};
|
|
2107
|
+
let refusal = list(
|
|
2108
|
+
&SourceUri::Civitai { version: 777 },
|
|
2109
|
+
&endpoints,
|
|
2110
|
+
&Anonymous,
|
|
2111
|
+
Deadline::none(),
|
|
2112
|
+
)
|
|
2113
|
+
.unwrap_err();
|
|
2114
|
+
assert_eq!(refusal.code, code);
|
|
2115
|
+
handle.join().unwrap();
|
|
2116
|
+
}
|
|
2008
2117
|
}
|
|
2009
2118
|
|
|
2010
2119
|
/// The credential is scoped to the host that holds it. A relative redirect stays on
|
|
@@ -2065,7 +2174,7 @@ mod tests {
|
|
|
2065
2174
|
// No downloadUrl in the fixture: the handler must fall back to the provider's
|
|
2066
2175
|
// canonical download route under the configured base.
|
|
2067
2176
|
let listing = format!(
|
|
2068
|
-
r#"{{"id":777,"files":[{{"name":"model.safetensors","sizeKB":273.4,"hashes":{{"SHA256":"{}"}}}}]}}"#,
|
|
2177
|
+
r#"{{"id":777,"files":[{{"id":92696,"name":"model.safetensors","sizeKB":273.4,"hashes":{{"SHA256":"{}"}}}}]}}"#,
|
|
2069
2178
|
object.sha256.to_ascii_uppercase()
|
|
2070
2179
|
);
|
|
2071
2180
|
let payload = body.clone();
|
|
@@ -2074,7 +2183,7 @@ mod tests {
|
|
|
2074
2183
|
let (base, _, handle) = origin(5, move |request| {
|
|
2075
2184
|
if request.path == "/api/v1/model-versions/777" {
|
|
2076
2185
|
ok_body(listing.as_bytes(), "application/json")
|
|
2077
|
-
} else if request.path == "/api/download/models/777" {
|
|
2186
|
+
} else if request.path == "/api/download/models/777?fileId=92696" {
|
|
2078
2187
|
if request.range.as_deref() == Some("bytes=0-0") {
|
|
2079
2188
|
format!(
|
|
2080
2189
|
"HTTP/1.1 206 Partial Content\r\nContent-Length: 1\r\nContent-Range: bytes 0-0/{length}\r\nETag: \"tag\"\r\nConnection: close\r\n\r\n\0"
|
|
@@ -8,12 +8,13 @@ use crate::{
|
|
|
8
8
|
ids::{prefixed, Doc, ObjectRef, Plain},
|
|
9
9
|
manifest::Manifest,
|
|
10
10
|
storage::HeldKey,
|
|
11
|
-
store::Store,
|
|
11
|
+
store::{Fault, Store},
|
|
12
12
|
};
|
|
13
13
|
use fs2::FileExt;
|
|
14
14
|
use std::{
|
|
15
15
|
fs::{self, File, OpenOptions},
|
|
16
16
|
io::{Read, Write},
|
|
17
|
+
os::unix::fs::OpenOptionsExt,
|
|
17
18
|
path::PathBuf,
|
|
18
19
|
};
|
|
19
20
|
|
|
@@ -272,6 +273,59 @@ pub fn create(store: &Store, owner: &str, tree: &Manifest) -> Result<TreeRoot> {
|
|
|
272
273
|
}
|
|
273
274
|
writer.finish(tree)
|
|
274
275
|
}
|
|
276
|
+
|
|
277
|
+
/// Import an exact ordinary-file roster under durable custody before admitting bytes.
|
|
278
|
+
/// A retry needs local paths only for members without a standing verification record.
|
|
279
|
+
pub fn import_tree(
|
|
280
|
+
store: &Store,
|
|
281
|
+
owner: &str,
|
|
282
|
+
tree: &Manifest,
|
|
283
|
+
files: &[(String, PathBuf)],
|
|
284
|
+
fault: &Fault,
|
|
285
|
+
) -> Result<TreeRoot> {
|
|
286
|
+
check_roster(owner, tree)?;
|
|
287
|
+
if tree.header().is_some() {
|
|
288
|
+
return refuse(Code::WRONG_TYPE, "import requires an ordinary-file tree");
|
|
289
|
+
}
|
|
290
|
+
let paths: std::collections::BTreeMap<_, _> = files.iter().map(|(k, v)| (k, v)).collect();
|
|
291
|
+
if paths.len() != files.len()
|
|
292
|
+
|| paths.len() != tree.entries().len()
|
|
293
|
+
|| tree
|
|
294
|
+
.entries()
|
|
295
|
+
.iter()
|
|
296
|
+
.any(|(member, _)| !paths.contains_key(member))
|
|
297
|
+
{
|
|
298
|
+
return refuse(
|
|
299
|
+
Code::TRANSACTION_CONFLICT,
|
|
300
|
+
"import paths differ from the exact manifest roster",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
let mut writer = Writer::open(store, owner, tree.object_ref())?;
|
|
304
|
+
if !writer.completed()? {
|
|
305
|
+
for (member, entry) in tree.entries() {
|
|
306
|
+
let object = entry.blob();
|
|
307
|
+
writer.hold_expected(object)?;
|
|
308
|
+
if !matches!(store.record_valid(&object.sha256), Ok(row) if row.length == object.length)
|
|
309
|
+
{
|
|
310
|
+
let path = paths[member];
|
|
311
|
+
if !fs::symlink_metadata(path).map_err(io)?.is_file() {
|
|
312
|
+
return refuse(Code::WRONG_TYPE, "import member must be a regular file");
|
|
313
|
+
}
|
|
314
|
+
let mut file = OpenOptions::new()
|
|
315
|
+
.read(true)
|
|
316
|
+
.custom_flags(crate::store::O_NOFOLLOW)
|
|
317
|
+
.open(path)
|
|
318
|
+
.map_err(io)?;
|
|
319
|
+
if !file.metadata().map_err(io)?.is_file() {
|
|
320
|
+
return refuse(Code::WRONG_TYPE, "import member must be a regular file");
|
|
321
|
+
}
|
|
322
|
+
store.put_stream(&mut file, Some(object), fault)?;
|
|
323
|
+
}
|
|
324
|
+
writer.landed(object)?;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
writer.finish(tree)
|
|
328
|
+
}
|
|
275
329
|
/// Adopt only from a still-retained complete owner, before releasing the source owner.
|
|
276
330
|
pub fn retain(store: &Store, source: &str, owner: &str) -> Result<TreeRoot> {
|
|
277
331
|
let _writer = WriterGuard::acquire(store.root())?;
|
|
@@ -459,7 +513,7 @@ pub fn conversion_roster(
|
|
|
459
513
|
.read_to_end(&mut bytes)
|
|
460
514
|
.map_err(io)?;
|
|
461
515
|
bytes
|
|
462
|
-
} else if member.ends_with(".safetensors") {
|
|
516
|
+
} else if member.ends_with(".safetensors") || crate::providers::is_civitai_member(member) {
|
|
463
517
|
let mut prefix = [0u8; 8];
|
|
464
518
|
file.read_exact(&mut prefix).map_err(io)?;
|
|
465
519
|
let length = u64::from_le_bytes(prefix);
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
fs,
|
|
3
|
+
path::{Path, PathBuf},
|
|
4
|
+
process::{Command, Stdio},
|
|
5
|
+
thread,
|
|
6
|
+
time::Duration,
|
|
7
|
+
};
|
|
8
|
+
use tensorfs_core::{
|
|
9
|
+
err::Code,
|
|
10
|
+
gc,
|
|
11
|
+
ids::{Doc, ObjectRef},
|
|
12
|
+
manifest::{Draft, Entry, Manifest},
|
|
13
|
+
source_artifact,
|
|
14
|
+
store::{Fault, Store},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
fn directory(label: &str) -> PathBuf {
|
|
18
|
+
std::env::temp_dir().join(format!(
|
|
19
|
+
"tfs-import-{label}-{}-{}",
|
|
20
|
+
std::process::id(),
|
|
21
|
+
tensorfs_core::meta::now_nanos_unique()
|
|
22
|
+
))
|
|
23
|
+
}
|
|
24
|
+
fn owner() -> String {
|
|
25
|
+
ObjectRef::of(b"local import").id()
|
|
26
|
+
}
|
|
27
|
+
fn tree() -> Manifest {
|
|
28
|
+
Manifest::from_files(vec![
|
|
29
|
+
("a.json".into(), ObjectRef::of(b"first member")),
|
|
30
|
+
("nested/b.bin".into(), ObjectRef::of(b"second member")),
|
|
31
|
+
])
|
|
32
|
+
.unwrap()
|
|
33
|
+
}
|
|
34
|
+
fn files(dir: &Path) -> Vec<(String, PathBuf)> {
|
|
35
|
+
vec![
|
|
36
|
+
("a.json".into(), dir.join("a")),
|
|
37
|
+
("nested/b.bin".into(), dir.join("b")),
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[test]
|
|
42
|
+
fn refuses_roster_and_typed_header_before_starting_custody() {
|
|
43
|
+
let dir = directory("roster");
|
|
44
|
+
let store = Store::init(&dir).unwrap();
|
|
45
|
+
let paths = files(&dir);
|
|
46
|
+
for bad in [
|
|
47
|
+
paths[..1].to_vec(),
|
|
48
|
+
vec![paths[0].clone(), paths[0].clone()],
|
|
49
|
+
vec![paths[0].clone(), ("extra".into(), dir.join("extra"))],
|
|
50
|
+
] {
|
|
51
|
+
assert_eq!(
|
|
52
|
+
source_artifact::import_tree(&store, &owner(), &tree(), &bad, &Fault::default())
|
|
53
|
+
.unwrap_err()
|
|
54
|
+
.code,
|
|
55
|
+
Code::TRANSACTION_CONFLICT
|
|
56
|
+
);
|
|
57
|
+
assert!(source_artifact::read(&store, &owner()).unwrap().is_none());
|
|
58
|
+
}
|
|
59
|
+
let typed = Draft {
|
|
60
|
+
entries: vec![(
|
|
61
|
+
"model.ct".into(),
|
|
62
|
+
Entry::CozyTensors(ObjectRef::of(b"header")),
|
|
63
|
+
)],
|
|
64
|
+
}
|
|
65
|
+
.seal()
|
|
66
|
+
.unwrap();
|
|
67
|
+
assert_eq!(
|
|
68
|
+
source_artifact::import_tree(&store, &owner(), &typed, &paths, &Fault::default())
|
|
69
|
+
.unwrap_err()
|
|
70
|
+
.code,
|
|
71
|
+
Code::WRONG_TYPE
|
|
72
|
+
);
|
|
73
|
+
assert!(source_artifact::read(&store, &owner()).unwrap().is_none());
|
|
74
|
+
fs::remove_dir_all(dir).unwrap();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[test]
|
|
78
|
+
fn verifies_bytes_and_resumes_only_missing_members() {
|
|
79
|
+
let dir = directory("verify");
|
|
80
|
+
let store = Store::init(&dir).unwrap();
|
|
81
|
+
fs::write(dir.join("a"), b"first member").unwrap();
|
|
82
|
+
fs::write(dir.join("b"), b"short").unwrap();
|
|
83
|
+
assert_eq!(
|
|
84
|
+
source_artifact::import_tree(&store, &owner(), &tree(), &files(&dir), &Fault::default())
|
|
85
|
+
.unwrap_err()
|
|
86
|
+
.code,
|
|
87
|
+
Code::LENGTH_MISMATCH
|
|
88
|
+
);
|
|
89
|
+
fs::write(dir.join("b"), b"wrong! member").unwrap();
|
|
90
|
+
assert_eq!(
|
|
91
|
+
source_artifact::import_tree(&store, &owner(), &tree(), &files(&dir), &Fault::default())
|
|
92
|
+
.unwrap_err()
|
|
93
|
+
.code,
|
|
94
|
+
Code::OBJECT_ID_MISMATCH
|
|
95
|
+
);
|
|
96
|
+
assert!(
|
|
97
|
+
!source_artifact::read(&store, &owner())
|
|
98
|
+
.unwrap()
|
|
99
|
+
.unwrap()
|
|
100
|
+
.complete
|
|
101
|
+
);
|
|
102
|
+
fs::remove_file(dir.join("a")).unwrap();
|
|
103
|
+
gc::collect(&dir, false).unwrap();
|
|
104
|
+
fs::write(dir.join("b"), b"second member").unwrap();
|
|
105
|
+
let result =
|
|
106
|
+
source_artifact::import_tree(&store, &owner(), &tree(), &files(&dir), &Fault::default())
|
|
107
|
+
.unwrap();
|
|
108
|
+
assert!(result.complete);
|
|
109
|
+
assert_eq!(result.manifest, tree().object_ref());
|
|
110
|
+
fs::remove_file(dir.join("b")).unwrap();
|
|
111
|
+
assert_eq!(
|
|
112
|
+
source_artifact::import_tree(&store, &owner(), &tree(), &files(&dir), &Fault::default())
|
|
113
|
+
.unwrap(),
|
|
114
|
+
result
|
|
115
|
+
);
|
|
116
|
+
let changed = Manifest::from_files(vec![("a.json".into(), ObjectRef::of(b"other"))]).unwrap();
|
|
117
|
+
assert_eq!(
|
|
118
|
+
source_artifact::import_tree(
|
|
119
|
+
&store,
|
|
120
|
+
&owner(),
|
|
121
|
+
&changed,
|
|
122
|
+
&files(&dir)[..1],
|
|
123
|
+
&Fault::default()
|
|
124
|
+
)
|
|
125
|
+
.unwrap_err()
|
|
126
|
+
.code,
|
|
127
|
+
Code::TRANSACTION_CONFLICT
|
|
128
|
+
);
|
|
129
|
+
let recipient = ObjectRef::of(b"recipient").id();
|
|
130
|
+
source_artifact::retain(&store, &owner(), &recipient).unwrap();
|
|
131
|
+
source_artifact::release(&store, &owner()).unwrap();
|
|
132
|
+
gc::collect(&dir, false).unwrap();
|
|
133
|
+
assert_eq!(store.read_manifest(&result.manifest).unwrap(), tree());
|
|
134
|
+
for (_, entry) in tree().entries() {
|
|
135
|
+
assert!(store.contains(&entry.blob().sha256));
|
|
136
|
+
}
|
|
137
|
+
assert_eq!(
|
|
138
|
+
source_artifact::import_tree(&store, &owner(), &tree(), &files(&dir), &Fault::default())
|
|
139
|
+
.unwrap_err()
|
|
140
|
+
.code,
|
|
141
|
+
Code::TRANSACTION_CLOSED
|
|
142
|
+
);
|
|
143
|
+
fs::remove_dir_all(dir).unwrap();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#[test]
|
|
147
|
+
fn import_child() {
|
|
148
|
+
let Some(dir) = std::env::var_os("TFS_IMPORT_TREE_CHILD") else {
|
|
149
|
+
return;
|
|
150
|
+
};
|
|
151
|
+
let dir = PathBuf::from(dir);
|
|
152
|
+
let store = Store::open(&dir).unwrap();
|
|
153
|
+
source_artifact::import_tree(
|
|
154
|
+
&store,
|
|
155
|
+
&owner(),
|
|
156
|
+
&tree(),
|
|
157
|
+
&files(&dir),
|
|
158
|
+
&Fault {
|
|
159
|
+
stage: Some("record".into()),
|
|
160
|
+
ready: Some(dir.join("ready")),
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
.unwrap();
|
|
164
|
+
panic!("fault should wait for the parent to kill this process");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[test]
|
|
168
|
+
fn symlink_member_is_refused_before_reading_its_target() {
|
|
169
|
+
let dir = directory("symlink");
|
|
170
|
+
let store = Store::init(&dir).unwrap();
|
|
171
|
+
fs::write(dir.join("target"), b"first member").unwrap();
|
|
172
|
+
std::os::unix::fs::symlink(dir.join("target"), dir.join("a")).unwrap();
|
|
173
|
+
assert_eq!(
|
|
174
|
+
source_artifact::import_tree(&store, &owner(), &tree(), &files(&dir), &Fault::default())
|
|
175
|
+
.unwrap_err()
|
|
176
|
+
.code,
|
|
177
|
+
Code::WRONG_TYPE
|
|
178
|
+
);
|
|
179
|
+
assert!(!store.contains(&ObjectRef::of(b"first member").sha256));
|
|
180
|
+
fs::remove_dir_all(dir).unwrap();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#[test]
|
|
184
|
+
fn killed_after_admission_before_landed_keeps_member_live_for_gc_and_resume() {
|
|
185
|
+
let dir = directory("kill");
|
|
186
|
+
let store = Store::init(&dir).unwrap();
|
|
187
|
+
fs::write(dir.join("a"), b"first member").unwrap();
|
|
188
|
+
fs::write(dir.join("b"), b"second member").unwrap();
|
|
189
|
+
let mut child = Command::new(std::env::current_exe().unwrap())
|
|
190
|
+
.args(["--exact", "import_child", "--nocapture"])
|
|
191
|
+
.env("TFS_IMPORT_TREE_CHILD", &dir)
|
|
192
|
+
.stdout(Stdio::null())
|
|
193
|
+
.spawn()
|
|
194
|
+
.unwrap();
|
|
195
|
+
for _ in 0..1000 {
|
|
196
|
+
if dir.join("ready").is_file() {
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
assert!(child.try_wait().unwrap().is_none());
|
|
200
|
+
thread::sleep(Duration::from_millis(5));
|
|
201
|
+
}
|
|
202
|
+
assert!(dir.join("ready").is_file());
|
|
203
|
+
child.kill().unwrap();
|
|
204
|
+
child.wait().unwrap();
|
|
205
|
+
let partial = source_artifact::read(&store, &owner()).unwrap().unwrap();
|
|
206
|
+
assert!(!partial.complete);
|
|
207
|
+
assert_eq!(partial.objects, vec![ObjectRef::of(b"first member")]);
|
|
208
|
+
gc::collect(&dir, false).unwrap();
|
|
209
|
+
fs::remove_file(dir.join("a")).unwrap();
|
|
210
|
+
let completed =
|
|
211
|
+
source_artifact::import_tree(&store, &owner(), &tree(), &files(&dir), &Fault::default())
|
|
212
|
+
.unwrap();
|
|
213
|
+
assert!(completed.complete);
|
|
214
|
+
gc::collect(&dir, false).unwrap();
|
|
215
|
+
assert_eq!(store.read_manifest(&completed.manifest).unwrap(), tree());
|
|
216
|
+
fs::remove_dir_all(dir).unwrap();
|
|
217
|
+
}
|
|
@@ -592,6 +592,29 @@ impl PyStore {
|
|
|
592
592
|
tree_root_dict(py, &root)
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
+
/// Import exact manifest members, retaining each member before byte admission.
|
|
596
|
+
fn import_tree<'p>(
|
|
597
|
+
&self,
|
|
598
|
+
py: Python<'p>,
|
|
599
|
+
owner: &str,
|
|
600
|
+
manifest_bytes: &[u8],
|
|
601
|
+
files: Vec<(String, PathBuf)>,
|
|
602
|
+
) -> PyResult<Bound<'p, PyDict>> {
|
|
603
|
+
let tree = Manifest::parse(manifest_bytes).or_refuse(py)?;
|
|
604
|
+
let root = py
|
|
605
|
+
.detach(|| {
|
|
606
|
+
tensorfs_core::source_artifact::import_tree(
|
|
607
|
+
&self.store,
|
|
608
|
+
owner,
|
|
609
|
+
&tree,
|
|
610
|
+
&files,
|
|
611
|
+
&Fault::default(),
|
|
612
|
+
)
|
|
613
|
+
})
|
|
614
|
+
.or_refuse(py)?;
|
|
615
|
+
tree_root_dict(py, &root)
|
|
616
|
+
}
|
|
617
|
+
|
|
595
618
|
fn retain_tree_root<'p>(
|
|
596
619
|
&self,
|
|
597
620
|
py: Python<'p>,
|
|
@@ -1654,13 +1677,13 @@ impl PyStore {
|
|
|
1654
1677
|
Ok(PyBytes::new(py, &v))
|
|
1655
1678
|
}
|
|
1656
1679
|
|
|
1657
|
-
/// Canonical manifest bytes and
|
|
1680
|
+
/// Canonical manifest bytes and an optional CozyTensors header selected by its typed entry.
|
|
1658
1681
|
fn manifest<'p>(&self, py: Python<'p>, hex: &str) -> PyResult<Bound<'p, PyDict>> {
|
|
1659
1682
|
let (m, mb, hb) = self.load(py, hex)?;
|
|
1660
1683
|
let d = PyDict::new(py);
|
|
1661
1684
|
d.set_item("manifest_id", m.manifest_id())?;
|
|
1662
1685
|
d.set_item("manifest", PyBytes::new(py, &mb))?;
|
|
1663
|
-
d.set_item("header", PyBytes::new(py,
|
|
1686
|
+
d.set_item("header", hb.as_deref().map(|bytes| PyBytes::new(py, bytes)))?;
|
|
1664
1687
|
Ok(d)
|
|
1665
1688
|
}
|
|
1666
1689
|
|
|
@@ -2491,7 +2514,7 @@ impl PyDerivedWriter {
|
|
|
2491
2514
|
}
|
|
2492
2515
|
|
|
2493
2516
|
impl PyStore {
|
|
2494
|
-
fn load(&self, py: Python<'_>, hex: &str) -> PyResult<(Manifest, Vec<u8>, Vec<u8
|
|
2517
|
+
fn load(&self, py: Python<'_>, hex: &str) -> PyResult<(Manifest, Vec<u8>, Option<Vec<u8>>)> {
|
|
2495
2518
|
let hex = hex.trim_start_matches("sha256:").to_string();
|
|
2496
2519
|
let len = std::fs::metadata(self.store.manifest_path(&hex))
|
|
2497
2520
|
.map(|m| m.len())
|
|
@@ -2501,24 +2524,12 @@ impl PyStore {
|
|
|
2501
2524
|
length: len,
|
|
2502
2525
|
};
|
|
2503
2526
|
let m = checkpoint::load_manifest(&self.store, &r).or_refuse(py)?;
|
|
2504
|
-
let
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
code: Code::MISSING_FIELD,
|
|
2511
|
-
detail: format!("manifest {} attaches no header", m.manifest_id()),
|
|
2512
|
-
},
|
|
2513
|
-
))
|
|
2514
|
-
}
|
|
2515
|
-
};
|
|
2516
|
-
let h = checkpoint::load_header(&self.store, &hr).or_refuse(py)?;
|
|
2517
|
-
Ok((
|
|
2518
|
-
m.clone(),
|
|
2519
|
-
m.canonical_bytes(),
|
|
2520
|
-
h.canonical_bytes().or_refuse(py)?,
|
|
2521
|
-
))
|
|
2527
|
+
let header = m
|
|
2528
|
+
.header()
|
|
2529
|
+
.map(|reference| checkpoint::load_header(&self.store, reference)?.canonical_bytes())
|
|
2530
|
+
.transpose()
|
|
2531
|
+
.or_refuse(py)?;
|
|
2532
|
+
Ok((m.clone(), m.canonical_bytes(), header))
|
|
2522
2533
|
}
|
|
2523
2534
|
}
|
|
2524
2535
|
|
|
@@ -112,7 +112,7 @@ class PreparedModelSource(TypedDict):
|
|
|
112
112
|
class ManifestRow(TypedDict):
|
|
113
113
|
manifest_id: str
|
|
114
114
|
manifest: bytes
|
|
115
|
-
header: bytes
|
|
115
|
+
header: bytes | None
|
|
116
116
|
|
|
117
117
|
class ReleaseRow(TypedDict):
|
|
118
118
|
org: str
|
|
@@ -423,6 +423,9 @@ class Store:
|
|
|
423
423
|
@property
|
|
424
424
|
def implementation_sha256(self) -> str: ...
|
|
425
425
|
def create_tree_root(self, owner: str, manifest_id: str, manifest_length: int) -> TreeRoot: ...
|
|
426
|
+
def import_tree(
|
|
427
|
+
self, owner: str, manifest_bytes: bytes, files: Sequence[tuple[str, StrPath]],
|
|
428
|
+
) -> TreeRoot: ...
|
|
426
429
|
def retain_tree_root(self, source_owner: str, owner: str) -> TreeRoot: ...
|
|
427
430
|
def tree_root(self, owner: str) -> TreeRoot | None: ...
|
|
428
431
|
def release_tree_root(self, owner: str) -> None: ...
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/examples/optional-cache-backfill.md
RENAMED
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/examples/resident_conversion_profile.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor
RENAMED
|
File without changes
|
{tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json
RENAMED
|
File without changes
|
{tensorfs-0.3.22 → tensorfs-0.3.24}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|