Coverage for python/lsst/daf/butler/script/removeRuns.py: 37%
45 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-10-10 01:29 -0700
« prev ^ index » next coverage.py v7.10.6, created at 2025-10-10 01:29 -0700
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 butler = Butler.from_config(repo)
89 with butler.registry.caching_context():
90 try:
91 collections = butler.collections.query_info(
92 collection,
93 CollectionType.RUN,
94 include_chains=False,
95 include_parents=True,
96 include_summary=True,
97 )
98 except MissingCollectionError:
99 # Act as if no collections matched.
100 collections = []
101 dataset_types = [dt.name for dt in butler.registry.queryDatasetTypes(...)]
102 dataset_types = list(butler.collections._filter_dataset_types(dataset_types, collections))
104 runs = []
105 datasets: dict[str, int] = defaultdict(int)
106 for collection_info in collections:
107 assert collection_info.type == CollectionType.RUN and collection_info.parents is not None
108 runs.append(RemoveRun(collection_info.name, list(collection_info.parents)))
109 with butler.query() as query:
110 for dt in dataset_types:
111 results = query.datasets(dt, collections=collection_info.name)
112 count = results.count(exact=False)
113 if count:
114 datasets[dt] += count
116 return runs, {k: datasets[k] for k in sorted(datasets.keys())}
119def removeRuns(
120 repo: str,
121 collection: str,
122) -> RemoveRunsResult:
123 """Remove collections.
125 Parameters
126 ----------
127 repo : `str`
128 Same as the ``config`` argument to ``Butler.__init__``.
129 collection : `str`
130 Same as the ``name`` argument to ``Butler.removeRuns``.
132 Returns
133 -------
134 collections : `RemoveRunsResult`
135 Contains information describing what will be removed.
136 """
137 runs, datasets = _getCollectionInfo(repo, collection)
139 def _doRemove(runs: Sequence[RemoveRun]) -> None:
140 """Perform the remove step."""
141 butler = Butler.from_config(repo, writeable=True)
143 butler.removeRuns([r.name for r in runs], unlink_from_chains=True)
145 result = RemoveRunsResult(
146 onConfirmation=partial(_doRemove, runs),
147 runs=runs,
148 datasets=datasets,
149 )
150 return result