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

1"""This package provides instance set support for Sparkle.""" 

2 

3from sparkle.instance.instances import ( 

4 MultiFileInstanceSet, 

5 FileInstanceSet, 

6 IterableFileInstanceSet, 

7 InstanceSet, 

8) 

9from pathlib import Path 

10 

11 

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) 

46 

47 

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. 

52 

53 The inverse of resolve_instance_name, which maps a (set, instance) back to its path. 

54 

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. 

62 

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. 

66 

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