compound_keys#
Hashable, serialisable keys built from two or more atomic string parts, with optional orientation and built-in rename / container-migration helpers.
Two concrete classes are provided:
CompoundKey— N ≥ 2 parts, general case.PairKey— exactly 2 parts; preferred API for pairs.CompoundKey.from_string/from_tupleauto-promote toPairKeywhen the result has exactly 2 parts.
Serialisation#
Both classes serialise to 'part0<sep>part1<sep>…' (default '-').
Unoriented keys are stored in sorted (canonical) order so that
PairKey('B', 'A') == PairKey('A', 'B').
Typical use#
>>> from ClearMap.config.compound_keys import PairKey, CompoundKey
>>> k = PairKey('cfos', 'dapi')
>>> str(k)
'cfos-dapi'
>>> k2 = PairKey.from_string('cfos-dapi')
>>> k == k2
True
- class CompoundKey(*parts: str, oriented: bool = False, sep: str = '-')[source]#
Bases:
objectAn immutable, hashable key composed of N ≥ 2 atomic string parts.
For the common two-part case prefer
PairKey, which adds.a/.baccessors and pair-specific container helpers.CompoundKey.from_stringandfrom_tuplereturn aPairKeyautomatically when the result has exactly two parts.Orientation#
oriented=False(default)Parts are sorted on construction so that key identity is independent of argument order.
oriented=TruePart order is preserved;
('A', 'B') != ('B', 'A').
- param *parts:
Two or more atomic string parts.
- type *parts:
str
- param oriented:
Whether part order is significant.
- type oriented:
bool
- param sep:
Separator used for string serialisation and parsing. Defaults to
DEFAULT_PAIR_SEP- type sep:
str
- classmethod canonical_str(key: str | tuple[str, ...], oriented: bool) str[source]#
Return the canonical (sorted) string representation of key.
Accepts either a serialised string or a raw tuple of parts.
- Parameters:
key (str | tuple[str, ...]) – Key to canonicalise.
oriented (bool) – Whether to treat the key as oriented before canonicalising.
- classmethod from_string(s: str, *, oriented: bool = False, sep: str = '-') CompoundKey | PairKey[source]#
Parse a serialised key string such as
'cfos-dapi'.Automatically returns a
PairKeywhen called onCompoundKeyand the string contains exactly one separator.- Parameters:
s (str) – Serialised key, e.g.
'a-b'or'a-b-c'.oriented (bool) – Whether part order is significant.
sep (str) – Expected separator character(s).
Returns
CompoundKey | PairKey
- Raises:
ValueError – If the string yields fewer parts than
_min_partsor a number inconsistent with_max_parts.
- classmethod from_tuple(parts: tuple[str, ...], *, oriented: bool = False, sep: str = '-') CompoundKey | PairKey[source]#
Construct from an existing tuple of parts.
Automatically returns a
PairKeywhen called onCompoundKeyandlen(parts) == 2.- Parameters:
parts (tuple[str, ...]) – Atomic parts.
oriented (bool) – Whether part order is significant.
sep (str) – Separator for serialisation.
Returns
CompoundKey | PairKey
- classmethod is_valid_key_str(s: object, *, sep: str = '-') bool[source]#
Return
Trueif s is a string that can be parsed by this class.Checks separator count against
_min_parts/_max_partsand ensures no part is empty or itself contains the separator.- Parameters:
s (object) – Candidate string.
sep (str) – Separator to split on.
- classmethod migrate_container_payload(container: Mapping[str, Any], *, new_key: str, rename_map: Mapping[str, str], oriented: bool) tuple[Any | None, bool][source]#
Look up a stored value for new_key, falling back to any plausible predecessor key produced by
candidate_ancestors_from_renames().Use this when a channel was renamed and the config entry was written under the old key: the new key will not be found directly, but a predecessor key will be.
- Parameters:
container (Mapping[str, Any]) – The config mapping to search.
new_key (str) – The compound-key string under which a value is desired.
rename_map (Mapping[str, str]) – Old → new name mapping used to derive candidate predecessors.
oriented (bool) – Whether keys should be treated as oriented.
Returns
- tuple[Optional[Any], bool]
(value, True)if a predecessor key was found;(None, False)otherwise.
- classmethod normalize_container_keys(cur: Mapping[str, Any], *, oriented: bool) tuple[dict[str, Any], bool][source]#
Re-serialise every valid compound-key string in cur to its canonical form (sorted for unoriented keys).
- Parameters:
cur (Mapping[str, Any]) – Source mapping whose keys may include compound-key strings.
oriented (bool) – Whether keys should be treated as oriented.
Returns
- tuple[dict[str, Any], bool]
(normalised_dict, changed).
- classmethod prune_container_invalid_atoms(container: Mapping[str, Any], *, allowed_atoms: set[str], oriented: bool) dict[str, Any][source]#
Remove entries whose compound-key string contains a part not in allowed_atoms.
- Parameters:
container (Mapping[str, Any]) – Source mapping to filter.
allowed_atoms (set[str]) – The set of permitted atomic part values.
oriented (bool) – Whether keys should be treated as oriented.
Returns
- dict[str, Any]
Filtered copy of container.
- classmethod rename_container_keys(cur: Mapping[str, Any], rename_map: Mapping[str, str], *, oriented: bool, max_hops: int = 32) tuple[dict[str, Any], bool][source]#
Apply rename_map to the parts of every compound-key string in cur.
- Parameters:
cur (Mapping[str, Any]) – Source mapping.
rename_map (Mapping[str, str]) – Old → new name mapping.
oriented (bool) – Whether keys should be treated as oriented.
max_hops (int) – Maximum transitive rename steps per part.
Returns
- tuple[dict[str, Any], bool]
(renamed_dict, changed).
- classmethod rename_key(key: str, rename_map: Mapping[str, str], *, oriented: bool, max_hops: int = 32) str[source]#
Parse key, apply rename_map to its parts, and return the serialised result.
- Parameters:
key (str) – Serialised key to rename.
rename_map (Mapping[str, str]) – Old → new name mapping.
oriented (bool) – Whether the key should be treated as oriented.
max_hops (int) – Maximum transitive rename steps per part.
- classmethod split_str(key: str, oriented: bool) tuple[str, ...][source]#
Parse key and return its parts as a tuple.
Convenience wrapper around
from_string()+as_tuple().- Parameters:
key (str) – Serialised key string.
oriented (bool) – Whether to treat the key as oriented.
- as_tuple() tuple[str, ...][source]#
Return parts as a plain tuple (same object as stored internally).
- candidate_ancestors_from_renames(rename_map: Mapping[str, str]) list[CompoundKey][source]#
Return every key that this key could have been before a rename.
Background#
Keys are stored in config under their name at the time of writing. When channels are renamed, the stored key becomes stale. This method generates all plausible old keys so callers can look them up and carry the stored value forward.
Algorithm#
Given rename_map {“old”: “new”, …}, invert it to find what each current part used to be called. Then take every combination of (current-or-predecessor) across all positions, discarding combos that equal self (nothing was renamed) or contain the same value twice (illegal key).
For oriented keys position order is preserved. For unoriented keys the constructor sorts parts into canonical form.
- param rename_map:
Maps old name → new name. Only direct (single-hop) renames are considered.
- type rename_map:
Mapping[str, str]
Examples
PairKey, oriented — two renames, one per side:
>>> PairKey('C', 'D', oriented=True).candidate_ancestors_from_renames({'A': 'C', 'B': 'D'}) [PairKey(a='A', b='B', oriented=True, sep='-'), PairKey(a='A', b='D', oriented=True, sep='-'), PairKey(a='C', b='B', oriented=True, sep='-')]
Position 0 was C, or A (which was renamed to C). Position 1 was D, or B (which was renamed to D). All combinations except self (‘C’,’D’) are returned.
PairKey, unoriented — same rename map:
>>> PairKey('C', 'D', oriented=False).candidate_ancestors_from_renames({'A': 'C', 'B': 'D'}) [PairKey(a='A', b='B', oriented=False, sep='-'), PairKey(a='A', b='D', oriented=False, sep='-'), PairKey(a='B', b='C', oriented=False, sep='-')]
Same combinations, but each pair is sorted by the constructor: (‘C’,’B’) becomes ‘B-C’.
PairKey — rename does not touch either part:
>>> PairKey('C', 'D', oriented=True).candidate_ancestors_from_renames({'X': 'Y'}) []
CompoundKey triplet, oriented:
>>> CompoundKey('C', 'D', 'E', oriented=True).candidate_ancestors_from_renames( ... {'A': 'C', 'B': 'E'}) [CompoundKey("A", "D", "B", oriented=True, sep="-"), CompoundKey("A", "D", "E", oriented=True, sep="-"), CompoundKey("C", "D", "B", oriented=True, sep="-")]
Position 0 options: {C, A}. Position 1 options: {D} (no rename). Position 2 options: {E, B}. Self (‘C’,’D’,’E’) is discarded.
CompoundKey triplet, unoriented:
>>> CompoundKey('C', 'D', 'E', oriented=False).candidate_ancestors_from_renames( ... {'A': 'C', 'B': 'E'}) [CompoundKey("A", "D", "B", oriented=False, sep="-"), CompoundKey("A", "D", "E", oriented=False, sep="-"), CompoundKey("B", "C", "D", oriented=False, sep="-")]
Same combinations; each triplet is sorted by the constructor: (‘C’,’D’,’B’) becomes (‘B’,’C’,’D’).
- canonical() CompoundKey[source]#
Return the unoriented (sorted) form of this key.
If the key is already unoriented, returns
selfunchanged. The returned object has the same concrete type (PairKeysubclass is preserved viatype(self)).
- rename(rename_map: Mapping[str, str], *, max_hops: int = 32) CompoundKey | PairKey[source]#
Return a new key with each part renamed according to rename_map.
Renames are applied transitively (up to max_hops steps). Unoriented keys are re-canonicalized as usual by the constructor after renaming.
- Parameters:
rename_map (Mapping[str, str]) – Old → new name mapping.
max_hops (int) – Maximum transitive rename steps per part.
- swapped() CompoundKey[source]#
Return a new key with parts in reversed order.
For a
PairKeythis swapsaandb. For longer keys parts are fully reversed. Theorientedflag is preserved.
- validate_atoms(allowed: Iterable[str]) bool#
Return
Trueif every part of this key is in allowed.- Parameters:
allowed (Iterable[str]) – The set of permitted atomic values.
- validate_parts(allowed: Iterable[str]) bool[source]#
Return
Trueif every part of this key is in allowed.- Parameters:
allowed (Iterable[str]) – The set of permitted atomic values.
- SEP: str = '-'#
- property n: int#
Number of parts.
- property oriented: bool#
Whether part order is significant for equality and hashing.
- property parts: tuple[str, ...]#
All parts as an immutable tuple.
- property sep: str#
Separator used for string serialisation.
- class PairKey(a: str, b: str, *, oriented: bool = False, sep: str = '-')[source]#
Bases:
CompoundKeyCompoundKeyspecialised for exactly two atomic parts.CompoundKeyclass-methods (from_string,from_tuple) automatically return aPairKeywhen the result has two parts, so callers rarely need to instantiate this class directly.All rename / container helpers defined on
CompoundKeywork unchanged;PairKeyonly adds.a/.bproperty accessors and narrows return-type annotations.- Parameters:
a (str) – The two atomic parts.
b (str) – The two atomic parts.
oriented (bool) – If
True,(a, b) != (b, a). IfFalse(default), parts are sorted so order does not matter.sep (str) – Separator used for string serialisation.
- SEP: str = '-'#
- property a: str#
First part (lower of the two for unoriented keys).
- property b: str#
Second part (higher of the two for unoriented keys).