12 This tool provides a command line interface to all the tasks related to the
13 :ref:`Conditions database <conditionsdb_overview>`: manage globaltags and iovs
14 as well as upload new payloads or download of existing payloads.
16 The usage of this tool is similar to git: there are sub commands like for
17 example ``tag`` which groups all actions related to the management of
18 globaltags. All the available commands are listed below.
20 Users need a valid JSON Web Token (JWT) to authenticate to the conditions
21 database when using this tool. For practical purposes, it is only necessary
22 to know that a JWT is a string containing crypted information, and that string
23 is stored in a file. More informations about what a JWT is can be found on
24 `Wikipedia <https://en.wikipedia.org/wiki/JSON_Web_Token>`_.
26 The tool automatically queries the JWT issuing server
27 (https://token.belle2.org) and gets a valid token by asking the B2MMS username
28 and password. The issued "default" JWT has a validity of 1 hour; after it
29 expires, a new JWT needs to be obtained for authenticating the conditions
30 database. When retrieved by this tool, the JWT is stored locally in the file
31 ``${HOME}/b2cdb_${BELLE2_USER}.token``.
33 Some selected users (e.g. the calibration managers) are granted a JWT with an
34 extended validity (30 days) to allow smooth operations with some automated
35 workflows. Such "extended" JWTs are issued by a different server
36 (https://token.belle2.org/extended). B2MMS username and password are necessary
37 for getting the extended JWT. The extended JWT differs from the default JWT
38 only by its validity and can be obtained only by manually querying the
39 alternative server. If queried via web browser, a file containing the extended
40 JWT will be downloaded in case the user is granted it. The server can also be
41 queried via command line using
42 ``wget --user USERNAME --ask-password --no-check-certificate https://token.belle2.org/extended``
43 or ``curl -u USERNAME -k https://token.belle2.org/extended``.
45 If the environment variable ``${BELLE2_CDB_AUTH_TOKEN}`` is defined and points
46 to a file containing a valid JWT, the ``b2conditionsdb`` tools use this token
47 to authenticate with the CDB instead of querying the issuing server. This is
48 useful when using an extended token. Please note that, in case the JWT to which
49 ``${BELLE2_CDB_AUTH_TOKEN}`` points is not valid anymore, the
50 ``b2conditionsdb`` tools will attempt to get a new one and store it into
51 ``${BELLE2_CDB_AUTH_TOKEN}``. If a new extended token is needed, it has to be
52 manually obtained via https://token.belle2.org/extended and stored into
53 ``${BELLE2_CDB_AUTH_TOKEN}``.
77 from basf2
import B2ERROR, B2WARNING, B2INFO, LogLevel, LogInfo, logging, \
80 from terminal_utils
import Pager
81 from dateutil.parser
import parse
as parse_date
82 from getpass
import getuser
83 from conditions_db
import ConditionsDB, enable_debugging, encode_name, PayloadInformation, set_cdb_authentication_token
90 from conditions_db.cli_management import command_tag_merge, command_tag_runningupdate, command_iovs, command_iovs_delete, command_iovs_copy, command_iovs_modify
93 def escape_ctrl_chars(name):
94 """Remove ANSI control characters from strings"""
96 if not hasattr(escape_ctrl_chars,
"_regex"):
97 escape_ctrl_chars._regex = re.compile(
"[\x00-\x1f\x7f-\x9f]")
101 if match.group(0).isspace():
102 return match.group(0)
103 return "\\x{:02x}".format(ord(match.group(0)))
105 return escape_ctrl_chars._regex.sub(escape, name)
108 def command_tag(args, db=None):
110 List, show, create, modify or clone globaltags.
112 This command allows to list, show, create modify or clone globaltags in the
113 central database. If no other command is given it will list all tags as if
114 "%(prog)s show" was given.
120 command_tag_list(args, db)
123 def command_tag_list(args, db=None):
125 List all available globaltags.
127 This command allows to list all globaltags, optionally limiting the output
128 to ones matching a given search term. By default invalidated globaltags
129 will not be included in the list, to show them as well please add
130 --with-invalid as option. Alternatively one can use --only-published to show
131 only tags which have been published
133 If the --regex option is supplied the search term will be interpreted as a
134 python regular expression where the case is ignored.
137 tagfilter = ItemFilter(args)
140 args.add_argument(
"--detail", action=
"store_true", default=
False,
141 help=
"show detailed information for all tags instead "
143 group = args.add_mutually_exclusive_group()
144 group.add_argument(
"--with-invalid", action=
"store_true", default=
False,
145 help=
"if given also invalidated tags will be shown")
146 group.add_argument(
"--only-published", action=
"store_true", default=
False,
147 help=
"if given only published tags will be shown")
148 tagfilter.add_arguments(
"tags")
152 if not tagfilter.check_arguments():
155 req = db.request(
"GET",
"/globalTags", f
"Getting list of globaltags{tagfilter}")
159 for item
in req.json():
160 if not tagfilter.check(item[
"name"]):
162 tagstatus = item[
"globalTagStatus"][
"name"]
163 if not getattr(args,
"with_invalid",
False)
and tagstatus ==
"INVALID":
165 if getattr(args,
"only_published",
False)
and tagstatus !=
"PUBLISHED":
170 taglist.sort(key=
lambda x: x[
"name"])
174 with Pager(
"List of globaltags{}{}".format(tagfilter,
" (detailed)" if getattr(args,
"detail",
False)
else ""),
True):
176 if getattr(args,
"detail",
False):
177 print_globaltag(db, item)
182 escape_ctrl_chars(item.get(
"description",
"")),
183 item[
"globalTagType"][
"name"],
184 item[
"globalTagStatus"][
"name"],
185 item[
"payloadCount"],
188 if not getattr(args,
"detail",
False):
189 table.insert(0, [
"id",
"name",
"description",
"type",
"status",
"# payloads"])
190 pretty_print_table(table, [-10, 0,
"*", -10, -10, -10], min_flexible_width=32)
193 def print_globaltag(db, *tags):
194 """ Print detailed globaltag information for the given globaltags side by side"""
195 results = [[
"id"], [
"name"], [
"description"], [
"type"], [
"status"],
196 [
"# payloads"], [
"created"], [
"modified"], [
"modified by"]]
201 if isinstance(info, str):
203 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(info)),
204 f
"Getting info for globaltag {info}")
205 except ConditionsDB.RequestError
as e:
213 created = parse_date(info[
"dtmIns"])
214 modified = parse_date(info[
"dtmMod"])
215 results[0].append(str(info[
"globalTagId"])),
216 results[1].append(info[
"name"]),
217 results[2].append(escape_ctrl_chars(info.get(
"description",
"")))
218 results[3].append(info[
"globalTagType"][
"name"])
219 results[4].append(info[
"globalTagStatus"][
"name"]),
220 results[5].append(info[
"payloadCount"]),
221 results[6].append(created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time"))
222 results[7].append(modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time"))
223 results[8].append(escape_ctrl_chars(info[
"modifiedBy"]))
225 ntags = len(results[0]) - 1
227 pretty_print_table(results, [11] + [
'*'] * ntags,
True)
231 def change_state(db, tag, state, force=False):
232 """Change the state of a global tag
234 If the new state is not revertible then ask for confirmation
236 state = state.upper()
237 if state
in [
"INVALID",
"PUBLISHED"]
and not force:
238 name = input(f
"ATTENTION: Marking a tag as {state} cannot be undone.\n"
239 "If you are sure you want to continue it please enter the tag name again: ")
241 B2ERROR(
"Names don't match, aborting")
244 db.request(
"PUT", f
"/globalTag/{encode_name(tag)}/updateGlobalTagStatus/{state}",
245 f
"Changing globaltag state {tag} to {state}")
248 def command_tag_show(args, db=None):
250 Show details about globaltags
252 This command will show details for the given globaltags like name,
253 description and number of payloads.
261 args.add_argument(
"tag", metavar=
"TAGNAME", nargs=
"+", help=
"globaltags to show")
266 with Pager(
"globaltag Information",
True):
268 ntags += print_globaltag(db, tag)
271 return len(args.tag) - ntags
274 def command_tag_create(args, db=None):
276 Create a new globaltag
278 This command creates a new globaltag in the database with the given name
279 and description. The name can only contain alpha-numeric characters and the
284 args.add_argument(
"type", metavar=
"TYPE", help=
"type of the globaltag to create, usually one of DEV or RELEASE")
285 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"name of the globaltag to create")
286 args.add_argument(
"description", metavar=
"DESCRIPTION", help=
"description of the globaltag")
287 args.add_argument(
"-u",
"--user", metavar=
"USER", help=
"username who created the tag. "
288 "If not given we will try to supply a useful default")
291 set_cdb_authentication_token(db, args.auth_token)
294 info = {
"name": args.tag,
"description": args.description,
"modifiedBy": args.user,
"isDefault":
False}
296 if args.user
is None:
297 info[
"modifiedBy"] = os.environ.get(
"BELLE2_USER", getuser())
299 typeinfo = db.get_globalTagType(args.type)
303 req = db.request(
"POST",
"/globalTag/{}".format(encode_name(typeinfo[
"name"])),
304 "Creating globaltag {name}".format(**info),
306 B2INFO(
"Successfully created globaltag {name} (id={globalTagId})".format(**req.json()))
309 def command_tag_modify(args, db=None):
311 Modify a globaltag by changing name or description
313 This command allows to change the name or description of an existing globaltag.
314 You can supply any combination of -n,-d,-t and only the given values will be changed
317 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to modify")
318 args.add_argument(
"-n",
"--name", help=
"new name")
319 args.add_argument(
"-d",
"--description", help=
"new description")
320 args.add_argument(
"-t",
"--type", help=
"new type of the globaltag")
321 args.add_argument(
"-u",
"--user", metavar=
"USER", help=
"username who created the tag. "
322 "If not given we will try to supply a useful default")
323 args.add_argument(
"-s",
"--state", help=
"new globaltag state, see the command ``tag state`` for details")
326 set_cdb_authentication_token(db, args.auth_token)
329 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(args.tag)),
330 f
"Getting info for globaltag {args.tag}")
334 old_name = info[
"name"]
336 for key
in [
"name",
"description"]:
337 value = getattr(args, key)
338 if value
is not None and value != info[key]:
342 info[
"modifiedBy"] = os.environ.get(
"BELLE2_USER", os.getlogin())
if args.user
is None else args.user
344 if args.type
is not None:
346 typeinfo = db.get_globalTagType(args.type)
350 if info[
'globalTagType'] != typeinfo:
351 info[
"globalTagType"] = typeinfo
356 db.request(
"PUT",
"/globalTag",
357 "Modifying globaltag {} (id={globalTagId})".format(old_name, **info),
360 if args.state
is not None:
361 name = args.name
if args.name
is not None else old_name
362 return change_state(db, name, args.state)
365 def command_tag_clone(args, db=None):
367 Clone a given globaltag including all IoVs
369 This command allows to clone a given globaltag with a new name but still
370 containing all the IoVs defined in the original globaltag.
374 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to be cloned")
375 args.add_argument(
"name", metavar=
"NEWNAME", help=
"name of the cloned globaltag")
378 set_cdb_authentication_token(db, args.auth_token)
381 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(args.tag)),
382 f
"Getting info for globaltag {args.tag}")
386 req = db.request(
"POST",
"/globalTags/{globalTagId}".format(**info),
387 "Cloning globaltag {name} (id={globalTagId})".format(**info))
391 cloned_info = req.json()
392 cloned_info[
"name"] = args.name
394 db.request(
"PUT",
"/globalTag",
"Renaming globaltag {name} (id={globalTagId})".format(**cloned_info),
398 def command_tag_state(args, db):
400 Change the state of a globaltag.
402 This command changes the state of a globaltag to the given value.
404 Usually the valid states are
407 Tag can be modified, payloads and iovs can be created and deleted. This
408 is the default state for new or cloned globaltags and is not suitable
409 for use in data analysis
411 Can be transitioned to TESTING, RUNNING
414 Tag cannot be modified and is suitable for testing but can be reopened
416 Can be transitioned to VALIDATED, OPEN
419 Tag cannot be modified and has been tested.
421 Can be transitioned to PUBLISHED, OPEN
424 Tag cannot be modified and is suitable for user analysis
426 Can only be transitioned to INVALID
429 Tag can only be modified by adding new runs, not modifying the payloads
433 Tag is invalid and should not be used for anything.
435 This state is end of life for a globaltag and cannot be transitioned to
438 .. versionadded:: release-04-00-00
441 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to be changed")
442 args.add_argument(
"state", metavar=
"STATE", help=
"new state for the globaltag")
443 args.add_argument(
"--force", default=
False, action=
"store_true", help=argparse.SUPPRESS)
446 set_cdb_authentication_token(db, args.auth_token)
448 return change_state(db, args.tag, args.state, args.force)
451 def remove_repeated_values(table, columns, keep=None):
452 """Strip repeated values from a table of values
454 This function takes a table (a list of lists with all the same length) and
455 removes values in certain columns if they are identical in consecutive rows.
457 It does this in a dependent way only if the previous columns are identical
458 will it continue stripping further columns. For example, given the table ::
467 If we want to remove duplicates in all columns in order it would look like this:
469 >>> remove_repeated_values(table, [0,1])
477 But we can give selected columns to strip one after the other
479 >>> remove_repeated_values(table, [1,0])
487 In addition, we might want to only strip some columns if previous columns
488 were identical but keep the values of the previous column. For this one can
491 >>> remove_repeated_values(table, [0,1,2], keep=[0])
500 table (list(list(str))): 2d table of values
501 columns (list(int)): indices of columns to consider in order
502 keep (set(int)): indices of columns to not strip
504 last_values = [
None] * len(columns)
505 for row
in table[1:]:
506 current_values = [row[i]
for i
in columns]
507 for i, curr, last
in zip(columns, current_values, last_values):
511 if keep
and i
in keep:
516 last_values = current_values
519 def command_diff(args, db):
520 """Compare two globaltags
522 This command allows to compare two globaltags. It will show the changes in
523 a format similar to a unified diff but by default it will not show any
524 context, only the new or removed payloads. Added payloads are marked with a
525 ``+`` in the first column, removed payloads with a ``-``
527 If ``--full`` is given it will show all payloads, even the ones common to
528 both globaltags. The differences can be limited to a given run and
529 limited to a set of payloads names using ``--filter`` or ``--exclude``. If
530 the ``--regex`` option is supplied the search term will be interpreted as a
531 python regular expression where the case is ignored.
533 .. versionchanged:: release-03-00-00
534 modified output structure and added ``--human-readable``
535 .. versionchanged:: after release-04-00-00
536 added parameter ``--checksums`` and ``--show-ids``
538 iovfilter = ItemFilter(args)
540 args.add_argument(
"--full", default=
False, action=
"store_true",
541 help=
"If given print all iovs, also those which are the same in both tags")
542 args.add_argument(
"--run", type=int, nargs=2, metavar=
"N", help=
"exp and run numbers "
543 "to limit showing iovs to a ones present in a given run")
544 args.add_argument(
"--human-readable", default=
False, action=
"store_true",
545 help=
"If given the iovs will be written in a more human friendly format. "
546 "Also repeated payload names will be omitted to create a more readable listing.")
547 args.add_argument(
"--checksums", default=
False, action=
"store_true",
548 help=
"If given don't show the revision number but the md5 checksum")
549 args.add_argument(
"--show-ids", default=
False, action=
"store_true",
550 help=
"If given also show the payload and iov ids for each iov")
552 args.add_argument(
"tagA", metavar=
"TAGNAME1", help=
"base for comparison")
553 args.add_argument(
"tagB", metavar=
"TAGNAME2", help=
"tagname to compare")
554 args.add_argument(
"--run-range", nargs=4, default=
None, type=int,
555 metavar=(
"FIRST_EXP",
"FIRST_RUN",
"FINAL_EXP",
"FINAL_RUN"),
556 help=
"Can be four numbers to limit the run range to be compared"
557 "Only iovs overlapping, even partially, with this range will be considered.")
558 iovfilter.add_arguments(
"payloads")
562 if not iovfilter.check_arguments():
565 with Pager(f
"Differences between globaltags {args.tagA} and {args.tagB}{iovfilter}",
True):
566 print(
"globaltags to be compared:")
567 ntags = print_globaltag(db, args.tagA, args.tagB)
572 e
for e
in db.get_all_iovs(
574 message=str(iovfilter),
575 run_range=args.run_range)
if iovfilter.check(
578 e
for e
in db.get_all_iovs(
580 message=str(iovfilter),
581 run_range=args.run_range)
if iovfilter.check(
584 B2INFO(
"Comparing contents ...")
585 diff = difflib.SequenceMatcher(a=listA, b=listB)
586 table = [[
"",
"Name",
"Rev" if not args.checksums
else "Checksum"]]
587 columns = [1,
"+", -8
if not args.checksums
else -32]
589 if args.human_readable:
593 table[0] += [
"First Exp",
"First Run",
"Final Exp",
"Final Run"]
594 columns += [6, 6, 6, 6]
597 table[0] += [
"IovId",
"PayloadId"]
600 def add_payloads(opcode, payloads):
601 """Add a list of payloads to the table, filling the first column with opcode"""
603 row = [opcode, p.name, p.revision
if not args.checksums
else p.checksum]
604 if args.human_readable:
605 row += [p.readable_iov()]
610 row += [p.iov_id, p.payload_id]
613 for tag, i1, i2, j1, j2
in diff.get_opcodes():
617 add_payloads(
" ", listB[j1:j2])
618 if tag
in [
"delete",
"replace"]:
619 add_payloads(
"-", listA[i1:i2])
620 if tag
in [
"insert",
"replace"]:
621 add_payloads(
"+", listB[j1:j2])
623 if args.human_readable:
627 remove_repeated_values(table, [0, 1, 2] + ([-1]
if args.show_ids
else []), keep=[0])
629 def color_row(row, widths, line):
630 if not LogPythonInterface.terminal_supports_colors():
632 begin = {
'+':
'\x1b[32m',
'-':
'\x1b[31m'}.get(row[0],
"")
634 return begin + line + end
639 print(f
" Differences between {args.tagA} and {args.tagB}")
640 pretty_print_table(table, columns, transform=color_row,
641 hline_formatter=
lambda w:
" " + (w - 1) *
'-')
644 def command_iov(args, db):
646 List all IoVs defined in a globaltag, optionally limited to a run range
648 This command lists all IoVs defined in a given globaltag. The list can be
649 limited to a given run and optionally searched using --filter or --exclude.
650 If the --regex option is supplied the search term will be interpreted as a
651 python regular expression where the case is ignored.
653 .. versionchanged:: release-03-00-00
654 modified output structure and added ``--human-readable``
655 .. versionchanged:: after release-04-00-00
656 added parameter ``--checksums`` and ``--show-ids``
657 .. versionchanged:: after release-08-00-04
658 added parameter ``--run-range``
661 iovfilter = ItemFilter(args)
664 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag for which the the IoVs should be listed")
665 args.add_argument(
"--run", type=int, nargs=2, metavar=
"N", help=
"exp and run numbers "
666 "to limit showing iovs to a ones present in a given run")
667 args.add_argument(
"--detail", action=
"store_true", default=
False,
668 help=
"if given show a detailed information for all "
669 "IoVs including details of the payloads")
670 args.add_argument(
"--human-readable", default=
False, action=
"store_true",
671 help=
"If given the iovs will be written in a more human friendly format. "
672 "Also repeated payload names will be omitted to create a more readable listing.")
673 args.add_argument(
"--checksums", default=
False, action=
"store_true",
674 help=
"If given don't show the revision number but the md5 checksum")
675 args.add_argument(
"--show-ids", default=
False, action=
"store_true",
676 help=
"If given also show the payload and iov ids for each iov")
677 args.add_argument(
"--run-range", nargs=4, default=
None, type=int,
678 metavar=(
"FIRST_EXP",
"FIRST_RUN",
"FINAL_EXP",
"FINAL_RUN"),
679 help=
"Can be four numbers to limit the run range to be shown"
680 "Only iovs overlapping, even partially, with this range will be shown.")
681 iovfilter.add_arguments(
"payloads")
685 if not iovfilter.check_arguments():
689 if db.get_globalTagInfo(args.tag)
is None:
690 B2ERROR(f
"Globaltag '{args.tag}' doesn't exist.")
693 run_range_str = f
' valid in {tuple(args.run_range)}' if args.run_range
else ''
694 args.run_range = IntervalOfValidity(args.run_range)
if args.run_range
else None
696 if args.run
is not None:
697 msg =
"Obtaining list of iovs for globaltag {tag}, exp={exp}, run={run}{filter}".format(
698 tag=args.tag, exp=args.run[0], run=args.run[1], filter=iovfilter)
699 req = db.request(
"GET",
"/iovPayloads", msg, params={
'gtName': args.tag,
'expNumber': args.run[0],
700 'runNumber': args.run[1]})
702 msg = f
"Obtaining list of iovs for globaltag {args.tag}{iovfilter}{run_range_str}"
703 req = db.request(
"GET", f
"/globalTag/{encode_name(args.tag)}/globalTagPayloads", msg)
705 with Pager(f
"List of IoVs{iovfilter}{run_range_str}{' (detailed)' if args.detail else ''}",
True):
707 for item
in req.json():
708 payload = item[
"payload" if 'payload' in item
else "payloadId"]
709 if "payloadIov" in item:
710 iovs = [item[
'payloadIov']]
712 iovs = item[
'payloadIovs']
714 if not iovfilter.check(payload[
'basf2Module'][
'name']):
718 if args.run_range
is not None:
719 if IntervalOfValidity(
720 iov[
'expStart'], iov[
'runStart'], iov[
'expEnd'], iov[
'runEnd']
727 iov_created = parse_date(iov[
"dtmIns"])
728 iov_modified = parse_date(iov[
"dtmMod"])
729 payload_created = parse_date(payload[
"dtmIns"])
730 payload_modified = parse_date(payload[
"dtmMod"])
732 [
"IoV Id", str(iov[
"payloadIovId"])],
733 [
"first experiment", iov[
"expStart"]],
734 [
"first run", iov[
"runStart"]],
735 [
"final experiment", iov[
"expEnd"]],
736 [
"final run", iov[
"runEnd"]],
737 [
"IoV created", iov_created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
738 [
"IoV modified", iov_modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
739 [
"IoV modified by", iov[
"modifiedBy"]],
740 [
"payload Id", str(payload[
"payloadId"])],
741 [
"name", payload[
"basf2Module"][
"name"]],
742 [
"revision", payload[
"revision"]],
743 [
"checksum", payload[
"checksum"]],
744 [
"payloadUrl", payload[
"payloadUrl"]],
745 [
"baseUrl", payload.get(
"baseUrl",
"")],
747 [
"payload created", payload_created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
748 [
"payload modified", payload_modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
749 [
"payload modified by", escape_ctrl_chars(payload[
"modifiedBy"])],
752 pretty_print_table(result, [-40,
'*'],
True)
754 payloads.append(PayloadInformation.from_json(payload, iov))
757 def add_ids(table, columns, payloads):
758 """Add the numerical ids to the table"""
760 table[0] += [
"IovId",
"PayloadId"]
762 for row, p
in zip(table[1:], payloads):
763 row += [p.iov_id, p.payload_id]
765 if args.human_readable:
766 table = [[
"Name",
"Rev" if not args.checksums
else "Checksum",
"IoV"]]
767 columns = [
"+", -8
if not args.checksums
else -32, -32]
768 table += [[p.name, p.revision
if not args.checksums
else p.checksum, p.readable_iov()]
for p
in payloads]
769 add_ids(table, columns, payloads)
771 remove_repeated_values(table, columns=[0, 1] + ([-1]
if args.show_ids
else []))
774 table = [[
"Name",
"Rev" if not args.checksums
else "Checksum",
"First Exp",
"First Run",
"Final Exp",
"Final Run"]]
775 table += [[p.name, p.revision
if not args.checksums
else p.checksum] + list(p.iov)
for p
in payloads]
776 columns = [
"+", -8
if not args.checksums
else -32, 6, 6, 6, 6]
777 add_ids(table, columns, payloads)
779 pretty_print_table(table, columns)
782 def command_dump(args, db):
784 Dump the content of a given payload
786 .. versionadded:: release-03-00-00
788 This command will dump the payload contents stored in a given payload. One
789 can either specify the payloadId (from a previous output of
790 ``b2conditionsdb iov``), the payload name and its revision in the central
791 database, or directly specify a local database payload file.
795 Dump the content of a previously downloaded payload file:
797 $ b2conditionsdb dump -f centraldb/dbstore_BeamParameters_rev_59449.root
799 Dump the content of a payload by name and revision directly from the central database:
801 $ b2conditionsdb dump -r BeamParameters 59449
803 Dump the content of the payload by name which is valid in a given globaltag
804 for a given experiment and run::
806 $ b2conditionsdb dump -g BeamParameters main_2021-08-04 0 0
808 Or directly by payload id from a previous call to ``b2conditionsdb iov``:
810 $ b2conditionsdb dump -i 59685
814 Depending on whether you want to display a payload by its id in the
815 database, its name and revision in the database or from a local file
816 provide **one** of the arguments ``-i``, ``-r``, ``-f`` or ``-g``
818 .. versionchanged:: after release-04-00-00
819 added argument ``-r`` to directly dump a payload valid for a given run
823 group = args.add_mutually_exclusive_group(required=
True)
824 choice = group.add_mutually_exclusive_group()
825 choice.add_argument(
"-i",
"--id", metavar=
"PAYLOADID", help=
"payload id to dump")
826 choice.add_argument(
"-r",
"--revision", metavar=(
"NAME",
"REVISION"), nargs=2,
827 help=
"Name and revision of the payload to dump")
828 choice.add_argument(
"-f",
"--file", metavar=
"FILENAME", help=
"Dump local payload file")
829 choice.add_argument(
"-g",
"--valid", metavar=(
"NAME",
"GLOBALTAG",
"EXP",
"RUN"), nargs=4,
830 help=
"Dump the payload valid for the given exp, run number in the given globaltag")
831 args.add_argument(
"--show-typenames", default=
False, action=
"store_true",
832 help=
"If given show the type names of all classes. "
833 "This makes output more crowded but can be helpful for complex objects.")
834 args.add_argument(
"--show-streamerinfo", default=
False, action=
"store_true",
835 help=
"If given show the StreamerInfo for the classes in the the payload file. "
836 "This can be helpful to find out which version of a payload object "
837 "is included and what are the members")
845 if not os.path.isfile(filename):
846 B2ERROR(f
"Payloadfile {filename} could not be found")
849 match = re.match(
r"^dbstore_(.*)_rev_(.*).root$", os.path.basename(filename))
851 match = re.match(
r"^(.*)_r(.*).root$", os.path.basename(filename))
853 B2ERROR(
"Filename doesn't follow database convention.\n"
854 "Should be 'dbstore_${payloadname}_rev_${revision}.root' or '${payloadname}_r${revision.root}'")
856 name = match.group(1)
857 revision = match.group(2)
858 payloadId =
"Unknown"
863 req = db.request(
"GET", f
"/payload/{args.id}",
"Getting payload info")
864 payload = PayloadInformation.from_json(req.json())
867 name, rev = args.revision
869 req = db.request(
"GET", f
"/module/{encode_name(name)}/payloads",
"Getting payload info")
871 if p[
"revision"] == rev:
872 payload = PayloadInformation.from_json(p)
875 B2ERROR(f
"Cannot find payload {name} with revision {rev}")
878 name, globaltag, exp, run = args.valid
880 for p
in db.get_all_iovs(globaltag, exp, run, f
", name={name}"):
881 if p.name == name
and (payload
is None or p.revision > payload.revision):
885 B2ERROR(f
"Cannot find payload {name} in globaltag {globaltag} for exp,run {exp},{run}")
888 filename = payload.url
889 revision = payload.revision
890 payloadId = payload.payload_id
894 from ROOT
import TFile, TBufferJSON, cout
897 tfile = TFile.Open(filename)
900 if not tfile
or not tfile.IsOpen():
902 contents = db._session.get(filename, stream=
True)
903 if contents.status_code != requests.codes.ok:
904 B2ERROR(f
"Could not open payload file {filename}")
906 raw_contents = contents.raw.read().decode()
908 obj = tfile.Get(name)
910 json_str = TBufferJSON.ConvertToJSON(obj)
914 Drop some members from ROOT json output.
916 We do not care about fBits, fUniqueID or the typename of sub classes,
917 we assume users are only interested in the data stored in the member
920 obj.pop(
"fBits",
None)
921 obj.pop(
"fUniqueID",
None)
922 if not args.show_typenames:
923 obj.pop(
"_typename",
None)
926 with Pager(f
"Contents of Payload {name}, revision {revision} (id {payloadId})",
True):
927 if args.show_streamerinfo
and tfile:
928 B2INFO(
"StreamerInfo of Payload {name}, revision {revision} (id {payloadId})")
929 tfile.ShowStreamerInfo()
936 if json_str
is not None:
937 B2INFO(f
"Contents of Payload {name}, revision {revision} (id {payloadId})")
940 obj = json.loads(json_str.Data(), object_hook=drop_fbits)
942 pprint.pprint(obj, compact=
True, width=shutil.get_terminal_size((80, 20))[0])
944 B2INFO(f
"Raw contents of Payload {name}, revision {revision} (id {payloadId})")
945 print(escape_ctrl_chars(raw_contents))
947 B2INFO(f
"ROOT contents of Payload {name}, revision {revision} (id {payloadId})")
948 B2WARNING(
"The payload is a valid ROOT file but doesn't contain a payload object with the expected name. "
949 " Automated display of file contents are not possible, showing just entries in the ROOT file.")
954 """Class to recursively show help for an ArgumentParser and all it's sub_parsers"""
957 """Print help message for given parser and call again for all sub parsers"""
959 subparsers_actions = [
960 action
for action
in parser._actions
961 if isinstance(action, argparse._SubParsersAction)]
964 for subparsers_action
in subparsers_actions:
966 for choice, subparser
in subparsers_action.choices.items():
968 print(f
"Command '{prefix}{choice}'")
969 print(subparser.format_help())
971 self.
print_subparsersprint_subparsers(subparser, prefix=f
"{prefix}{choice} ")
973 def __call__(self, parser, namespace, values, option_string=None):
974 """Show full help message"""
976 with Pager(f
"{parser.prog} {option_string}"):
982 def get_argument_parser():
984 Build a parser with all arguments of all commands
987 options = argparse.ArgumentParser(add_help=
False)
988 options.add_argument(
"--debugging", action=
"store_true",
989 help=
"Enable debugging of http traffic")
990 options.add_argument(
"--help-full", action=FullHelpAction,
991 help=
"show help message for all commands and exit")
992 options.add_argument(
"--base-url", default=
None,
993 help=
"URI for the base of the REST API, if not given a list of default locations is tried")
994 options.add_argument(
"--auth-token", type=str, default=
None,
995 help=
"JSON Web Token necessary for authenticating to the conditions database. "
996 "Useful only for debugging, since by default the tool automatically "
997 "gets a token for you by asking the B2MMS username and password. "
998 "If the environment variable ``$BELLE2_CDB_AUTH_TOKEN`` points to a file with a valid "
999 "token, such token is used (useful for automatic workflows).")
1001 parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, parents=[options])
1002 parser.set_defaults(func=
lambda x, y: parser.print_help())
1003 parsers = parser.add_subparsers(
1004 title=
"Top level commands",
1005 description=
"To get additional help, run '%(prog)s COMMAND --help'"
1010 for name, func
in sorted(globals().items()):
1011 if not name.startswith(
"command_"):
1016 parts = name.split(
'_')[1:]
1022 parent_parser, parent = subparsers[tuple(parts[:-1])]
1026 parent = parent_parser.add_subparsers(
1027 title=
"sub commands",
1028 description=
"To get additional help, run '%(prog)s COMMAND --help'"
1030 subparsers[tuple(parts[:-1])][1] = parent
1035 helptxt, description = textwrap.dedent(func.__doc__).split(
"\n\n", 1)
1036 command_parser = parent.add_parser(parts[-1], help=helptxt, add_help=
True, description=description,
1037 parents=[options], formatter_class=argparse.RawDescriptionHelpFormatter)
1040 func(command_parser,
None)
1042 command_parser.set_defaults(func=func)
1044 subparsers[tuple(parts)] = [command_parser,
None]
1049 def create_symlinks(base):
1050 """Create symlinks from base to all subcommands.
1052 e.g. if the base is ``b2conditionsdb`` then this command will create symlinks
1053 like ``b2conditionsdb-tag-show`` in the same directory
1055 When adding a new command to b2conditionsdb this function needs to be executed
1056 in the framework tools directory
1058 python3 -c 'from conditions_db import cli_main; cli_main.create_symlinks("b2conditionsdb")'
1064 for name
in sorted(globals().keys()):
1065 if not name.startswith(
"command_"):
1067 parts = name.split(
"_")[1:]
1068 if parts
in excluded:
1070 dest = base +
"-".join([
""] + parts)
1074 except FileNotFoundError:
1076 print(f
"create symlink {dest}")
1077 os.symlink(base, dest)
1082 Main function for the command line interface.
1084 it will automatically create an ArgumentParser including all functions which
1085 start with command_ in the global namespace as sub commands. These
1086 functions should take the arguments as first argument and an instance of the
1087 ConditionsDB interface as second argument. If the db interface is None the
1088 first argument is an instance of argparse.ArgumentParser an in this case the
1089 function should just add all needed arguments to the argument parser and
1094 logging.enable_summary(
False)
1096 logging.enable_python_logging =
True
1098 for level
in LogLevel.values.values():
1099 logging.set_info(level, LogInfo.LEVEL | LogInfo.MESSAGE)
1103 sys.argv[0:1] = os.path.basename(sys.argv[0]).split(
'-')
1106 parser = get_argument_parser()
1110 args = parser.parse_args()
1117 nprocess = getattr(args,
"nprocess", 1)
1118 retries = getattr(args,
"retries", 0)
1121 B2WARNING(
"-j must be larger than zero, ignoring")
1122 args.nprocess = nprocess = 1
1124 conditions_db =
ConditionsDB(args.base_url, nprocess, retries)
1127 return args.func(args, conditions_db)
def print_subparsers(self, parser, prefix="")
def __call__(self, parser, namespace, values, option_string=None)
int intersect(const TRGCDCLpar &lp1, const TRGCDCLpar &lp2, CLHEP::HepVector &v1, CLHEP::HepVector &v2)
intersection
int main(int argc, char **argv)
Run all tests.