Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-39412: Add Jenkins build ID to ap_verify Sasquatch metadata #132

Merged
merged 2 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 65 additions & 0 deletions python/lsst/analysis/tools/bin/verifyToSasquatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
]

import argparse
import copy
import datetime
import logging
from collections import defaultdict
Expand Down Expand Up @@ -79,6 +80,14 @@ def makeParser():
"date, e.g. 2021-06-30T22:28:25Z. If not provided, the run time or "
"current time is used.",
)
parser.add_argument(
"--extra",
action=_AppendDict,
help="Extra field (in the form key=value) to be added to any records "
"uploaded to Sasquatch. See SasquatchDispatcher.dispatch and "
".dispatchRef for more details. The --extra argument can be passed "
"multiple times.",
)

api_group = parser.add_argument_group("Sasquatch API arguments")
api_group.add_argument(
Expand All @@ -97,6 +106,61 @@ def makeParser():
return parser


class _AppendDict(argparse.Action):
"""An action analogous to the build-in 'append' that appends to a `dict`
instead of a `list`.

Inputs are assumed to be strings in the form "key=value"; any input that
does not contain exactly one "=" character is invalid. If the default value
is non-empty, the default key-value pairs may be overwritten by values from
the command line.
"""

def __init__(
self,
option_strings,
dest,
nargs=None,
const=None,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar=None,
):
if default is None:
default = {}
if not isinstance(default, Mapping):
argname = option_strings if option_strings else metavar if metavar else dest
raise TypeError(f"Default for {argname} must be a mapping or None, got {default!r}.")
super().__init__(option_strings, dest, nargs, const, default, type, choices, required, help, metavar)

def __call__(self, parser, namespace, values, option_string=None):
# argparse doesn't make defensive copies, so namespace.dest may be
# the same object as self.default. Do the copy ourselves and avoid
# modifying the object previously in namespace.dest.
mapping = copy.copy(getattr(namespace, self.dest))

# Sometimes values is a copy of default instead of an input???
if isinstance(values, Mapping):
mapping.update(values)
else:
# values may be either a string or list of strings, depending on
# nargs. Unsafe to test for Sequence, because a scalar string
# passes.
if not isinstance(values, list):
kfindeisen marked this conversation as resolved.
Show resolved Hide resolved
values = [values]
for value in values:
vars = value.split("=")
if len(vars) != 2:
raise ValueError(f"Argument {value!r} does not match format 'key=value'.")
mapping[vars[0]] = vars[1]

# Other half of the defensive copy.
setattr(namespace, self.dest, mapping)


def _bundle_metrics(
butler: Butler, metricValues: Iterable[DatasetRef]
) -> Mapping[tuple[str, str, DataCoordinate], MetricMeasurementBundle]:
Expand Down Expand Up @@ -167,4 +231,5 @@ def main():
timestamp=args.date_created,
datasetIdentifier=args.dataset,
identifierFields=dataId,
extraFields=args.extra,
)
103 changes: 102 additions & 1 deletion tests/test_verifyToSasquatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import argparse
import tempfile
import unittest

import astropy.units as u
import lsst.daf.butler.tests as butlerTests
from lsst.analysis.tools.bin.verifyToSasquatch import _bundle_metrics
from lsst.analysis.tools.bin.verifyToSasquatch import _AppendDict, _bundle_metrics
from lsst.analysis.tools.interfaces import MetricMeasurementBundle
from lsst.daf.butler import CollectionType, DataCoordinate
from lsst.verify import Measurement
Expand Down Expand Up @@ -175,3 +176,103 @@ def test_bundle_metrics_badmetric(self):
refs = self.butler.registry.queryDatasets("metricvalue_nopackage_notAMetric", collections=...)
with self.assertRaises(ValueError):
_bundle_metrics(self.butler, refs)


class AppendDictTestSuite(unittest.TestCase):
def setUp(self):
super().setUp()

self.testbed = argparse.ArgumentParser()

def test_default_none_positional(self):
self.testbed.add_argument("test", action=_AppendDict, nargs="*")

namespace = self.testbed.parse_args([])
self.assertEqual(namespace.test, {})

namespace = self.testbed.parse_args("baz=bak".split())
self.assertEqual(namespace.test, {"baz": "bak"})

def test_default_none_keyword(self):
self.testbed.add_argument("--test", action=_AppendDict)

namespace = self.testbed.parse_args([])
self.assertEqual(namespace.test, {})

namespace = self.testbed.parse_args("--test baz=bak".split())
self.assertEqual(namespace.test, {"baz": "bak"})

def test_default_empty_positional(self):
self.testbed.add_argument("test", action=_AppendDict, default={}, nargs="*")

namespace = self.testbed.parse_args([])
self.assertEqual(namespace.test, {})

namespace = self.testbed.parse_args("baz=bak".split())
self.assertEqual(namespace.test, {"baz": "bak"})

def test_default_empty_keyword(self):
self.testbed.add_argument("--test", action=_AppendDict, default={})

namespace = self.testbed.parse_args([])
self.assertEqual(namespace.test, {})

namespace = self.testbed.parse_args("--test baz=bak".split())
self.assertEqual(namespace.test, {"baz": "bak"})

def test_default_non_empty_positional(self):
self.testbed.add_argument("test", action=_AppendDict, default={"foo": "bar"}, nargs="*")

namespace = self.testbed.parse_args([])
self.assertEqual(namespace.test, {"foo": "bar"})

namespace = self.testbed.parse_args("baz=bak".split())
self.assertEqual(namespace.test, {"foo": "bar", "baz": "bak"})

namespace = self.testbed.parse_args("foo=fum".split())
self.assertEqual(namespace.test, {"foo": "fum"})

def test_default_non_empty_keyword(self):
self.testbed.add_argument("--test", action=_AppendDict, default={"foo": "bar"})

namespace = self.testbed.parse_args([])
self.assertEqual(namespace.test, {"foo": "bar"})

namespace = self.testbed.parse_args("--test baz=bak".split())
self.assertEqual(namespace.test, {"foo": "bar", "baz": "bak"})

namespace = self.testbed.parse_args("--test foo=fum".split())
self.assertEqual(namespace.test, {"foo": "fum"})

def test_default_invalid(self):
with self.assertRaises(TypeError):
self.testbed.add_argument("test", action=_AppendDict, default="bovine")
with self.assertRaises(TypeError):
self.testbed.add_argument("test", action=_AppendDict, default=[])

def test_multi_append(self):
self.testbed.add_argument("--test", action=_AppendDict)

namespace = self.testbed.parse_args("--test foo=bar --test baz=bak".split())
self.assertEqual(namespace.test, {"foo": "bar", "baz": "bak"})

def test_multi_nargs_append(self):
self.testbed.add_argument("--test", action=_AppendDict, nargs="*")

namespace = self.testbed.parse_args("--test foo=bar fee=fum --test baz=bak --test".split())
self.assertEqual(namespace.test, {"foo": "bar", "fee": "fum", "baz": "bak"})

def test_emptyvalue(self):
self.testbed.add_argument("test", action=_AppendDict)

namespace = self.testbed.parse_args("foo=".split())
self.assertEqual(namespace.test, {"foo": ""})

def test_nopair(self):
self.testbed.add_argument("test", action=_AppendDict)

with self.assertRaises(ValueError):
self.testbed.parse_args("foo".split())

with self.assertRaises(ValueError):
self.testbed.parse_args("assertion=beauty=truth".split())