Coverage for python / lsst / daf / butler / script / removeRuns.py: 38%
44 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-17 16:22 -0800
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-17 16:22 -0800
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/>.
27from __future__ import annotations
29from collections import defaultdict
30from collections.abc import Callable, Mapping, Sequence
31from dataclasses import dataclass
32from functools import partial
34from .._butler import Butler
35from .._collection_type import CollectionType
36from ..registry import MissingCollectionError
39@dataclass
40class RemoveRun:
41 """Represents a RUN collection to remove."""
43 # the name of the run:
44 name: str
45 # parent CHAINED collections the RUN belongs to:
46 parents: list[str]
49@dataclass
50class RemoveRunsResult:
51 """Container to return to the cli command.
53 Contains the names of runs that will be deleted, and a map of dataset type
54 to how many of that dataset will be deleted. Also contains the callback
55 function to execute the remove upon user confirmation.
56 """
58 # the callback function to do the removal
59 onConfirmation: Callable[[], None]
60 # list of the run collections that will be removed
61 runs: Sequence[RemoveRun]
62 # mapping of dataset type name to how many will be removed.
63 datasets: Mapping[str, int]
66def _getCollectionInfo(
67 repo: str,
68 collection: str,
69) -> tuple[list[RemoveRun], Mapping[str, int]]:
70 """Get the names and types of collections that match the collection
71 string.
73 Parameters
74 ----------
75 repo : `str`
76 The URI to the repository.
77 collection : `str`
78 The collection string to search for. Same as the `expression`
79 argument to `registry.queryCollections`.
81 Returns
82 -------
83 runs : `list` of `RemoveRun`
84 Describes the runs that will be removed.
85 datasets : `dict` [`str`, `int`]
86 The dataset types and and how many will be removed.
87 """
88 with Butler.from_config(repo) as butler, butler.registry.caching_context():
89 try:
90 collections = butler.collections.query_info(
91 collection,
92 CollectionType.RUN,
93 include_chains=False,
94 include_parents=True,
95 include_summary=True,
96 )
97 except MissingCollectionError:
98 # Act as if no collections matched.
99 collections = []
100 dataset_types = [dt.name for dt in butler.registry.queryDatasetTypes(...)]
101 dataset_types = list(butler.collections._filter_dataset_types(dataset_types, collections))
103 runs = []
104 datasets: dict[str, int] = defaultdict(int)
105 for collection_info in collections:
106 assert collection_info.type == CollectionType.RUN and collection_info.parents is not None
107 runs.append(RemoveRun(collection_info.name, list(collection_info.parents)))
108 with butler.query() as query:
109 for dt in dataset_types:
110 results = query.datasets(dt, collections=collection_info.name)
111 count = results.count(exact=False)
112 if count:
113 datasets[dt] += count
115 return runs, {k: datasets[k] for k in sorted(datasets.keys())}
118def removeRuns(
119 repo: str,
120 collection: str,
121) -> RemoveRunsResult:
122 """Remove collections.
124 Parameters
125 ----------
126 repo : `str`
127 Same as the ``config`` argument to ``Butler.__init__``.
128 collection : `str`
129 Same as the ``name`` argument to ``Butler.removeRuns``.
131 Returns
132 -------
133 collections : `RemoveRunsResult`
134 Contains information describing what will be removed.
135 """
136 runs, datasets = _getCollectionInfo(repo, collection)
138 def _doRemove(runs: Sequence[RemoveRun]) -> None:
139 """Perform the remove step."""
140 with Butler.from_config(repo, writeable=True) as butler:
141 butler.removeRuns([r.name for r in runs], unlink_from_chains=True)
143 result = RemoveRunsResult(
144 onConfirmation=partial(_doRemove, runs),
145 runs=runs,
146 datasets=datasets,
147 )
148 return result