Coverage for python/lsst/daf/butler/script/collectionChain.py: 7%
44 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-10-02 08:00 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-10-02 08:00 +0000
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
28from __future__ import annotations
30from collections.abc import Iterable
32from .._butler import Butler
33from ..registry import CollectionType, MissingCollectionError
36def collectionChain(
37 repo: str, mode: str, parent: str, children: Iterable[str], doc: str | None, flatten: bool
38) -> tuple[str, ...]:
39 """Get the collections whose names match an expression.
41 Parameters
42 ----------
43 repo : `str`
44 URI to the location of the repo or URI to a config file describing the
45 repo and its location.
46 mode : `str`
47 Update mode for this chain. Options are:
48 'redefine': Create or modify ``parent`` to be defined by the supplied
49 ``children``.
50 'remove': Modify existing chain to remove ``children`` from it.
51 'prepend': Add the given ``children`` to the beginning of the chain.
52 'extend': Modify existing chain to add ``children`` to the end of it.
53 'pop': Pop a numbered element off the chain. Defaults to popping
54 the first element (0). ``children`` must be integers if given.
55 Both 'prepend' and 'extend' are the same as 'redefine' if the chain
56 does not exist.
57 parent: `str`
58 Name of the chained collection to update. Will be created if it
59 does not exist already.
60 children: iterable of `str`
61 Names of the children to be included in the chain.
62 doc : `str`
63 If the chained collection is being created, the documentation string
64 that will be associated with it.
65 flatten : `bool`
66 If `True`, recursively flatten out any nested
67 `~CollectionType.CHAINED` collections in ``children`` first.
69 Returns
70 -------
71 chain : `tuple` of `str`
72 The collections in the chain following this command.
73 """
74 butler = Butler(repo, writeable=True, without_datastore=True)
76 # Every mode needs children except pop.
77 if not children and mode != "pop":
78 raise RuntimeError(f"Must provide children when defining a collection chain in mode {mode}.")
80 try:
81 butler.registry.getCollectionType(parent)
82 except MissingCollectionError:
83 # Create it -- but only if mode can work with empty chain.
84 if mode in ("redefine", "extend", "prepend"):
85 if not doc:
86 doc = None
87 butler.registry.registerCollection(parent, CollectionType.CHAINED, doc)
88 else:
89 raise RuntimeError(
90 f"Mode '{mode}' requires that the collection exists "
91 f"but collection '{parent}' is not known to this registry"
92 ) from None
94 current = list(butler.registry.getCollectionChain(parent))
96 if mode == "redefine":
97 # Given children are what we want.
98 pass
99 elif mode == "prepend":
100 children = tuple(children) + tuple(current)
101 elif mode == "extend":
102 current.extend(children)
103 children = current
104 elif mode == "remove":
105 for child in children:
106 current.remove(child)
107 children = current
108 elif mode == "pop":
109 if children:
110 n_current = len(current)
112 def convert_index(i: int) -> int:
113 """Convert negative index to positive."""
114 if i >= 0:
115 return i
116 return n_current + i
118 # For this mode the children should be integers.
119 # Convert negative integers to positive ones to allow
120 # sorting.
121 indices = [convert_index(int(child)) for child in children]
123 # Reverse sort order so we can remove from the end first
124 indices = sorted(indices, reverse=True)
126 else:
127 # Nothing specified, pop from the front of the chain.
128 indices = [0]
130 for i in indices:
131 current.pop(i)
133 children = current
134 else:
135 raise ValueError(f"Unrecognized update mode: '{mode}'")
137 butler.registry.setCollectionChain(parent, children, flatten=flatten)
139 return tuple(butler.registry.getCollectionChain(parent))