Skip to content

Commit f7258ea

Browse files
authored
Merge pull request #22170 from jmchilton/fix_paired_unpaired_map_over
More paired_or_unpaired fixes.
2 parents e994848 + 17d1e22 commit f7258ea

26 files changed

+3109
-133
lines changed

doc/source/dev/collection_semantics.md

Lines changed: 421 additions & 63 deletions
Large diffs are not rendered by default.

lib/galaxy/model/dataset_collections/type_description.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,34 @@ def effective_collection_type(self, subcollection_type):
7171
if subcollection_type == "single_datasets":
7272
return self.collection_type
7373

74+
normalized = _normalize_collection_type(self.collection_type)
75+
normalized_sub = _normalize_collection_type(subcollection_type)
76+
77+
if subcollection_type == "paired_or_unpaired":
78+
if self.collection_type.endswith(":paired"):
79+
# paired_or_unpaired consumes the :paired suffix
80+
return self.collection_type[: -len(":paired")]
81+
elif normalized.endswith("list"):
82+
# paired_or_unpaired acts like single_datasets for collections
83+
# whose innermost type is list (each element wrapped as unpaired)
84+
return self.collection_type
85+
else:
86+
# strip last rank (paired_or_unpaired consumes it)
87+
return self.collection_type[: self.collection_type.rfind(":")]
88+
89+
if normalized_sub.endswith(":paired_or_unpaired"):
90+
# Compound :paired_or_unpaired suffix — iterative peel-off matching
91+
# TS effectiveMapOver logic. Strip ranks from both sides, then
92+
# optionally strip one more if :paired was consumed.
93+
current = self.collection_type
94+
current_other = subcollection_type
95+
while ":" in current_other:
96+
current_other = current_other[: current_other.rfind(":")]
97+
current = current[: current.rfind(":")]
98+
if normalized.endswith(":paired"):
99+
current = current[: current.rfind(":")]
100+
return current
101+
74102
return self.collection_type[: -(len(subcollection_type) + 1)]
75103

76104
def has_subcollections_of_type(self, other_collection_type) -> bool:
@@ -88,12 +116,23 @@ def has_subcollections_of_type(self, other_collection_type) -> bool:
88116
other_collection_type = _normalize_collection_type(other_collection_type)
89117
if collection_type == other_collection_type:
90118
return False
91-
if collection_type.endswith(other_collection_type):
119+
if collection_type.endswith(f":{other_collection_type}"):
92120
return True
93121
if other_collection_type == "paired_or_unpaired":
94122
# this can be thought of as a subcollection of anything except a pair
95123
# since it would match a pair exactly
96124
return collection_type != "paired"
125+
if other_collection_type.endswith(":paired_or_unpaired"):
126+
# Compound :paired_or_unpaired suffix — e.g. list:list can map over
127+
# list:paired_or_unpaired. Strip the :paired_or_unpaired to get the
128+
# required higher ranks, optionally strip :paired from self (since
129+
# paired_or_unpaired consumes paired), then check alignment.
130+
higher_ranks_required = other_collection_type[: other_collection_type.rfind(":")]
131+
if collection_type.endswith(":paired"):
132+
higher_ranks = collection_type[: collection_type.rfind(":")]
133+
else:
134+
higher_ranks = collection_type
135+
return higher_ranks.endswith(higher_ranks_required) and higher_ranks != higher_ranks_required
97136
if other_collection_type == "single_datasets":
98137
# effectively any collection has unpaired subcollections
99138
return True

0 commit comments

Comments
 (0)