-
Notifications
You must be signed in to change notification settings - Fork 12
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-24584: create an ingestRaws butler command #279
Changes from all commits
acf0d2c
04a17c5
821b0dc
0b348ea
dfc6cc5
3c03176
ab72dae
0628bc9
f8d561b
a85e71b
f4300c0
d790736
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# This file is part of daf_butler. | ||
# | ||
# Developed for the LSST Data Management System. | ||
# This product includes software developed by the LSST Project | ||
# (http://www.lsst.org). | ||
# See the COPYRIGHT file at the top-level directory of this distribution | ||
# for details of code ownership. | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
|
||
import click | ||
|
||
from ..utils import split_kv | ||
|
||
|
||
class config_option: # noqa: N801 | ||
def __init__(self, required=False, help=None): | ||
self.required = required | ||
|
||
def __call__(self, f): | ||
return click.option("-c", "--config", | ||
required=self.required, | ||
callback=split_kv, | ||
multiple=True, | ||
help="Config override, as a key-value pair.")(f) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# This file is part of daf_butler. | ||
# | ||
# Developed for the LSST Data Management System. | ||
# This product includes software developed by the LSST Project | ||
# (http://www.lsst.org). | ||
# See the COPYRIGHT file at the top-level directory of this distribution | ||
# for details of code ownership. | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
|
||
import click | ||
|
||
|
||
class config_file_option: # noqa: N801 | ||
def __init__(self, required=False, help=None): | ||
self.required = required | ||
|
||
def __call__(self, f): | ||
return click.option("-C", "--config-file", | ||
required=self.required, | ||
type=click.STRING, | ||
help="The path to the config file.")(f) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,9 +19,20 @@ | |
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
import click | ||
import os | ||
|
||
from ..core.utils import iterable | ||
|
||
|
||
# DAF_BUTLER_MOCK is set by some tests as an environment variable and indicates | ||
# to the cli_handle_exception function that instead of executing the command | ||
# implementation function it should print details about the called command to | ||
# stdout. These details are then used to verify the command function was loaded | ||
# and received expected inputs. | ||
DAF_BUTLER_MOCK = {"DAF_BUTLER_MOCK": ""} | ||
|
||
|
||
def split_commas(context, param, values): | ||
"""Process a tuple of values, where each value may contain comma-separated | ||
values, and return a single list of all the passed-in values. | ||
|
@@ -31,9 +42,13 @@ def split_commas(context, param, values): | |
|
||
Parameters | ||
---------- | ||
context : click.Context | ||
|
||
values : tuple of string | ||
context : `click.Context` or `None` | ||
The current execution context. Unused, but Click always passes it to | ||
callbacks. | ||
param : `click.core.Option` or `None` | ||
The parameter being handled. Unused, but Click always passes it to | ||
callbacks. | ||
values : [`str`] | ||
All the values passed for this option. Strings may contain commas, | ||
which will be treated as delimiters for separate values. | ||
|
||
|
@@ -49,6 +64,56 @@ def split_commas(context, param, values): | |
return valueList | ||
|
||
|
||
def split_kv(context, param, values, separator="="): | ||
"""Process a tuple of values that are key-value pairs separated by a given | ||
separator. Multiple pairs may be comma separated. Return a dictionary of | ||
all the passed-in values. | ||
|
||
This function can be passed to the 'callback' argument of a click.option to | ||
allow it to process comma-separated values (e.g. "--my-opt a=1,b=2"). | ||
|
||
Parameters | ||
---------- | ||
context : `click.Context` or `None` | ||
The current execution context. Unused, but Click always passes it to | ||
callbacks. | ||
param : `click.core.Option` or `None` | ||
The parameter being handled. Unused, but Click always passes it to | ||
callbacks. | ||
values : [`str`] | ||
All the values passed for this option. Strings may contain commas, | ||
which will be treated as delimiters for separate values. | ||
separator : str, optional | ||
The character that separates key-value pairs. May not be a comma or an | ||
empty space (for space separators use Click's default implementation | ||
for tuples; `type=(str, str)`). By default "=". | ||
|
||
Returns | ||
------- | ||
`dict` : [`str`, `str`] | ||
The passed-in values in dict form. | ||
|
||
Raises | ||
------ | ||
`click.ClickException` | ||
Raised if the separator is not found in an entry, or if duplicate keys | ||
are encountered. | ||
""" | ||
if "," == separator or " " == separator: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sometimes for future expansion possibilities it's better to write this as if separator in (",", " "): |
||
raise RuntimeError(f"'{separator}' is not a supported separator for key-value pairs.") | ||
vals = split_commas(context, param, values) | ||
ret = {} | ||
for val in vals: | ||
try: | ||
k, v = val.split(separator) | ||
except ValueError: | ||
raise click.ClickException(f"Missing or invalid key-value separator in value '{val}'") | ||
if k in ret: | ||
raise click.ClickException(f"Duplicate entries for '{k}' in '{values}'") | ||
ret[k] = v | ||
return ret | ||
|
||
|
||
def to_upper(context, param, value): | ||
"""Convert a value to upper case. | ||
|
||
|
@@ -65,3 +130,82 @@ def to_upper(context, param, value): | |
A copy of the passed-in value, converted to upper case. | ||
""" | ||
return value.upper() | ||
|
||
|
||
def printFunctionInfo(func, *args, **kwargs): | ||
"""For unit testing butler subcommand call execution, write a dict to | ||
stdout that formats information about a funciton call into a dict that can | ||
be evaluated by `verifyFunctionInfo` | ||
|
||
Parameters | ||
---------- | ||
func : function | ||
The function that has been called, whose name should be written. | ||
args : [`str`] | ||
The values of the arguments the function was called with. | ||
kwags : `dict` [`str`, `str`] | ||
The names and values of the kwargs the function was called with. | ||
""" | ||
print(dict(function=func.__name__, | ||
args=args, | ||
kwargs=kwargs)) | ||
|
||
|
||
def verifyFunctionInfo(testSuite, output, function, expectedArgs, expectedKwargs): | ||
"""For unit testing butler subcommand call execution, compare a dict that | ||
has been printed to stdout to expected data. | ||
|
||
Parameters | ||
---------- | ||
testSuite : `unittest.Testsuite` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure this isn't a |
||
The test suite that is executing a unit test. | ||
output : `str` | ||
The dict that has been printed to stdout. It should be formatted to | ||
re-instantiate by calling `eval`. | ||
function : `str` | ||
The name of the function that was was expected to have been called. | ||
expectedArgs : [`str`] | ||
The values of the arguments that should have been passed to the | ||
function. | ||
expectedKwargs : `dict` [`str`, `str`] | ||
The names and values of the kwargs that should have been passsed to the | ||
funciton. | ||
""" | ||
calledWith = eval(output) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we are going to be using |
||
testSuite.assertEqual(calledWith['function'], function) | ||
testSuite.assertEqual(calledWith['args'], expectedArgs) | ||
testSuite.assertEqual(calledWith['kwargs'], expectedKwargs) | ||
|
||
|
||
def cli_handle_exception(func, *args, **kwargs): | ||
"""Wrap a function call in an exception handler that raises a | ||
ClickException if there is an Exception. | ||
|
||
Also provides support for unit testing by testing for an environment | ||
variable, and if it is present prints the function name, args, and kwargs | ||
to stdout so they can be read and verified by the unit test code. | ||
|
||
Parameters | ||
---------- | ||
func : function | ||
A function to be called and exceptions handled. Will pass args & kwargs | ||
to the function. | ||
|
||
Returns | ||
------- | ||
The result of calling func. | ||
|
||
Raises | ||
------ | ||
click.ClickException | ||
An exception to be handled by the Click CLI tool. | ||
""" | ||
# "DAF_BUTLER_MOCK" matches the key in the variable DAF_BUTLER_MOCK, | ||
# defined in the top of this file. | ||
if "DAF_BUTLER_MOCK" in os.environ: | ||
printFunctionInfo(func, *args, **kwargs) | ||
return | ||
try: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to implement this with a unittest.mock, but couldn't find a way to get the called mock object back out to the calling script for verification. I couldn't pass it in because butler wants to import the command, and even when replacing the object in sys.modules, import still provides a copy of the class, not the class itself :-( |
||
return func(*args, **kwargs) | ||
except Exception as err: | ||
raise click.ClickException(err) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# This file is part of obs_base. | ||
# | ||
# Developed for the LSST Data Management System. | ||
# This product includes software developed by the LSST Project | ||
# (https://www.lsst.org). | ||
# See the COPYRIGHT file at the top-level directory of this distribution | ||
# for details of code ownership. | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
from .createRepo import createRepo |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# This file is part of daf_butler. | ||
# | ||
# Developed for the LSST Data Management System. | ||
# This product includes software developed by the LSST Project | ||
# (http://www.lsst.org). | ||
# See the COPYRIGHT file at the top-level directory of this distribution | ||
# for details of code ownership. | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
from .. import Butler, Config | ||
|
||
|
||
def createRepo(repo, config_file=None, standalone=False, override=False, outfile=None): | ||
"""Create an empty Gen3 Butler repository. | ||
|
||
Parameters | ||
---------- | ||
repo : `str` | ||
URI to the location to create the repo. | ||
config_file : `str` or `None` | ||
Path to a config yaml file, by default None | ||
standalone : `bool` | ||
Include all the defaults in the config file in the repo if True. | ||
Insulates the the repo from changes to package defaults. By default | ||
False. | ||
override : `bool` | ||
Allow values in the config file to override any repo settings, by | ||
default False. | ||
outfile : `str` or None | ||
Name of output file to receive repository configuration. Default is to | ||
write butler.yaml into the specified repo, by default False. | ||
""" | ||
config = Config(config_file) if config_file is not None else None | ||
Butler.makeRepo(repo, config=config, standalone=standalone, forceConfigRoot=not override, | ||
outfile=outfile) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It reads like the variable here is the contents of the environment variable but environment variables can't be a dict so is the key in the dict the environment variable name and the value in the dict what is set by the user? Having the global use the same name as the key is confusing here.