Coverage for src/sparkle/instance/__init__.py: 92%
26 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-08 12:00 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-08 12:00 +0000
1"""This package provides instance set support for Sparkle."""
3from sparkle.instance.instances import (
4 MultiFileInstanceSet,
5 FileInstanceSet,
6 IterableFileInstanceSet,
7 InstanceSet,
8)
9from pathlib import Path
12def Instance_Set(target: any) -> InstanceSet:
13 """The combined interface for all instance set types."""
14 if (
15 isinstance(target, Path)
16 and (target / MultiFileInstanceSet.instance_csv).exists()
17 ) or (
18 isinstance(target, list)
19 and isinstance(target[0], Path)
20 and (target[0].parent / MultiFileInstanceSet.instance_csv).exists()
21 ):
22 return MultiFileInstanceSet(target)
23 elif (not target.exists()) and (
24 target.parent / MultiFileInstanceSet.instance_csv
25 ).exists():
26 # Single instance
27 return MultiFileInstanceSet(target)
28 elif (
29 isinstance(target, Path)
30 and target.is_dir()
31 and all(
32 [
33 p.suffix in IterableFileInstanceSet.supported_filetypes
34 for p in target.iterdir()
35 ]
36 )
37 ):
38 return IterableFileInstanceSet(target)
39 elif not target.exists(): # Resolve suffix
40 alternatives = [p for p in target.parent.iterdir()]
41 for alt in alternatives:
42 if target.name == alt.stem:
43 target = alt
44 break
45 return FileInstanceSet(target)
48def resolve_instance_pair(
49 instance_path: Path | list[Path],
50) -> tuple[str, str] | list[tuple[str, str]]:
51 """Resolve instance file path(s) to their canonical (set_name, instance_name) pairs.
53 The inverse of resolve_instance_name, which maps a (set, instance) back to its path.
55 The data frames are keyed by the pair, but the CLIs that write to them only receive
56 file paths. The instance name cannot be derived from the path, because each
57 InstanceSet subclass names its instances differently (FileInstanceSet uses the
58 stem, IterableFileInstanceSet the full name with suffix, and MultiFileInstanceSet
59 reads them from its instances.csv). Rather than guess, reconstruct the owning set
60 from the parent directory and look each pair up by path, so the subclass supplies
61 its own naming convention.
63 Args:
64 instance_path: A single instance file path, or a list of paths. Each path is
65 resolved independently to its own pair.
67 Returns:
68 For a single Path, the (set_name, instance_name) pair. For a list of paths, the
69 list of pairs, one per path, in the same order as the input. A pair falls back to
70 (parent directory name, file stem) when the path matches no instance.
71 """
72 single = isinstance(instance_path, Path)
73 instance_paths = [instance_path] if single else instance_path
74 instance_pairs = []
75 for path in instance_paths:
76 target = path.resolve()
77 # Instance_Set() picks the same subclass (file / iterable / multi-file) that was
78 # used originally, so its instance_pairs carry the exact stored naming convention.
79 instance_set = Instance_Set(path.parent)
80 resolved_pair = next(
81 (
82 pair
83 for pair, pair_path in zip(
84 instance_set.instance_pairs, instance_set.instance_paths
85 )
86 # pair_path may be a list (multi-file instance), so normalise to a list.
87 if target
88 in [
89 file.resolve()
90 for file in (
91 pair_path if isinstance(pair_path, list) else [pair_path]
92 )
93 ]
94 ),
95 (path.parent.name, path.stem),
96 )
97 instance_pairs.append(resolved_pair)
98 # A single Path in yields its pair directly, a list yields a pair per path.
99 return instance_pairs[0] if single else instance_pairs