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
92 def escape_ctrl_chars(name):
93 """Remove ANSI control characters from strings"""
95 if not hasattr(escape_ctrl_chars,
"_regex"):
96 escape_ctrl_chars._regex = re.compile(
"[\x00-\x1f\x7f-\x9f]")
100 if match.group(0).isspace():
101 return match.group(0)
102 return "\\x{:02x}".format(ord(match.group(0)))
104 return escape_ctrl_chars._regex.sub(escape, name)
107 def command_tag(args, db=None):
109 List, show, create, modify or clone globaltags.
111 This command allows to list, show, create modify or clone globaltags in the
112 central database. If no other command is given it will list all tags as if
113 "%(prog)s show" was given.
119 command_tag_list(args, db)
122 def command_tag_list(args, db=None):
124 List all available globaltags.
126 This command allows to list all globaltags, optionally limiting the output
127 to ones matching a given search term. By default invalidated globaltags
128 will not be included in the list, to show them as well please add
129 --with-invalid as option. Alternatively one can use --only-published to show
130 only tags which have been published
132 If the --regex option is supplied the search term will be interpreted as a
133 python regular expression where the case is ignored.
136 tagfilter = ItemFilter(args)
139 args.add_argument(
"--detail", action=
"store_true", default=
False,
140 help=
"show detailed information for all tags instead "
142 group = args.add_mutually_exclusive_group()
143 group.add_argument(
"--with-invalid", action=
"store_true", default=
False,
144 help=
"if given also invalidated tags will be shown")
145 group.add_argument(
"--only-published", action=
"store_true", default=
False,
146 help=
"if given only published tags will be shown")
147 tagfilter.add_arguments(
"tags")
151 if not tagfilter.check_arguments():
154 req = db.request(
"GET",
"/globalTags", f
"Getting list of globaltags{tagfilter}")
158 for item
in req.json():
159 if not tagfilter.check(item[
"name"]):
161 tagstatus = item[
"globalTagStatus"][
"name"]
162 if not getattr(args,
"with_invalid",
False)
and tagstatus ==
"INVALID":
164 if getattr(args,
"only_published",
False)
and tagstatus !=
"PUBLISHED":
169 taglist.sort(key=
lambda x: x[
"name"])
173 with Pager(
"List of globaltags{}{}".format(tagfilter,
" (detailed)" if getattr(args,
"detail",
False)
else ""),
True):
175 if getattr(args,
"detail",
False):
176 print_globaltag(db, item)
181 escape_ctrl_chars(item.get(
"description",
"")),
182 item[
"globalTagType"][
"name"],
183 item[
"globalTagStatus"][
"name"],
184 item[
"payloadCount"],
187 if not getattr(args,
"detail",
False):
188 table.insert(0, [
"id",
"name",
"description",
"type",
"status",
"# payloads"])
189 pretty_print_table(table, [-10, 0,
"*", -10, -10, -10], min_flexible_width=32)
192 def print_globaltag(db, *tags):
193 """ Print detailed globaltag information for the given globaltags side by side"""
194 results = [[
"id"], [
"name"], [
"description"], [
"type"], [
"status"],
195 [
"# payloads"], [
"created"], [
"modified"], [
"modified by"]]
200 if isinstance(info, str):
202 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(info)),
203 f
"Getting info for globaltag {info}")
204 except ConditionsDB.RequestError
as e:
212 created = parse_date(info[
"dtmIns"])
213 modified = parse_date(info[
"dtmMod"])
214 results[0].append(str(info[
"globalTagId"])),
215 results[1].append(info[
"name"]),
216 results[2].append(escape_ctrl_chars(info.get(
"description",
"")))
217 results[3].append(info[
"globalTagType"][
"name"])
218 results[4].append(info[
"globalTagStatus"][
"name"]),
219 results[5].append(info[
"payloadCount"]),
220 results[6].append(created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time"))
221 results[7].append(modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time"))
222 results[8].append(escape_ctrl_chars(info[
"modifiedBy"]))
224 ntags = len(results[0]) - 1
226 pretty_print_table(results, [11] + [
'*'] * ntags,
True)
230 def change_state(db, tag, state, force=False):
231 """Change the state of a global tag
233 If the new state is not revertible then ask for confirmation
235 state = state.upper()
236 if state
in [
"INVALID",
"PUBLISHED"]
and not force:
237 name = input(f
"ATTENTION: Marking a tag as {state} cannot be undone.\n"
238 "If you are sure you want to continue it please enter the tag name again: ")
240 B2ERROR(
"Names don't match, aborting")
243 db.request(
"PUT", f
"/globalTag/{encode_name(tag)}/updateGlobalTagStatus/{state}",
244 f
"Changing globaltag state {tag} to {state}")
247 def command_tag_show(args, db=None):
249 Show details about globaltags
251 This command will show details for the given globaltags like name,
252 description and number of payloads.
260 args.add_argument(
"tag", metavar=
"TAGNAME", nargs=
"+", help=
"globaltags to show")
265 with Pager(
"globaltag Information",
True):
267 ntags += print_globaltag(db, tag)
270 return len(args.tag) - ntags
273 def command_tag_create(args, db=None):
275 Create a new globaltag
277 This command creates a new globaltag in the database with the given name
278 and description. The name can only contain alpha-numeric characters and the
283 args.add_argument(
"type", metavar=
"TYPE", help=
"type of the globaltag to create, usually one of DEV or RELEASE")
284 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"name of the globaltag to create")
285 args.add_argument(
"description", metavar=
"DESCRIPTION", help=
"description of the globaltag")
286 args.add_argument(
"-u",
"--user", metavar=
"USER", help=
"username who created the tag. "
287 "If not given we will try to supply a useful default")
290 set_cdb_authentication_token(db, args.auth_token)
293 info = {
"name": args.tag,
"description": args.description,
"modifiedBy": args.user,
"isDefault":
False}
295 if args.user
is None:
296 info[
"modifiedBy"] = os.environ.get(
"BELLE2_USER", getuser())
298 typeinfo = db.get_globalTagType(args.type)
302 req = db.request(
"POST",
"/globalTag/{}".format(encode_name(typeinfo[
"name"])),
303 "Creating globaltag {name}".format(**info),
305 B2INFO(
"Successfully created globaltag {name} (id={globalTagId})".format(**req.json()))
308 def command_tag_modify(args, db=None):
310 Modify a globaltag by changing name or description
312 This command allows to change the name or description of an existing globaltag.
313 You can supply any combination of -n,-d,-t and only the given values will be changed
316 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to modify")
317 args.add_argument(
"-n",
"--name", help=
"new name")
318 args.add_argument(
"-d",
"--description", help=
"new description")
319 args.add_argument(
"-t",
"--type", help=
"new type of the globaltag")
320 args.add_argument(
"-u",
"--user", metavar=
"USER", help=
"username who created the tag. "
321 "If not given we will try to supply a useful default")
322 args.add_argument(
"-s",
"--state", help=
"new globaltag state, see the command ``tag state`` for details")
325 set_cdb_authentication_token(db, args.auth_token)
328 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(args.tag)),
329 f
"Getting info for globaltag {args.tag}")
333 old_name = info[
"name"]
335 for key
in [
"name",
"description"]:
336 value = getattr(args, key)
337 if value
is not None and value != info[key]:
341 info[
"modifiedBy"] = os.environ.get(
"BELLE2_USER", os.getlogin())
if args.user
is None else args.user
343 if args.type
is not None:
345 typeinfo = db.get_globalTagType(args.type)
349 if info[
'globalTagType'] != typeinfo:
350 info[
"globalTagType"] = typeinfo
355 db.request(
"PUT",
"/globalTag",
356 "Modifying globaltag {} (id={globalTagId})".format(old_name, **info),
359 if args.state
is not None:
360 name = args.name
if args.name
is not None else old_name
361 return change_state(db, name, args.state)
364 def command_tag_clone(args, db=None):
366 Clone a given globaltag including all IoVs
368 This command allows to clone a given globaltag with a new name but still
369 containing all the IoVs defined in the original globaltag.
373 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to be cloned")
374 args.add_argument(
"name", metavar=
"NEWNAME", help=
"name of the cloned globaltag")
377 set_cdb_authentication_token(db, args.auth_token)
380 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(args.tag)),
381 f
"Getting info for globaltag {args.tag}")
385 req = db.request(
"POST",
"/globalTags/{globalTagId}".format(**info),
386 "Cloning globaltag {name} (id={globalTagId})".format(**info))
390 cloned_info = req.json()
391 cloned_info[
"name"] = args.name
393 db.request(
"PUT",
"/globalTag",
"Renaming globaltag {name} (id={globalTagId})".format(**cloned_info),
397 def command_tag_state(args, db):
399 Change the state of a globaltag.
401 This command changes the state of a globaltag to the given value.
403 Usually the valid states are
406 Tag can be modified, payloads and iovs can be created and deleted. This
407 is the default state for new or cloned globaltags and is not suitable
408 for use in data analysis
410 Can be transitioned to TESTING, RUNNING
413 Tag cannot be modified and is suitable for testing but can be reopened
415 Can be transitioned to VALIDATED, OPEN
418 Tag cannot be modified and has been tested.
420 Can be transitioned to PUBLISHED, OPEN
423 Tag cannot be modified and is suitable for user analysis
425 Can only be transitioned to INVALID
428 Tag can only be modified by adding new runs, not modifying the payloads
432 Tag is invalid and should not be used for anything.
434 This state is end of life for a globaltag and cannot be transitioned to
437 .. versionadded:: release-04-00-00
440 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to be changed")
441 args.add_argument(
"state", metavar=
"STATE", help=
"new state for the globaltag")
442 args.add_argument(
"--force", default=
False, action=
"store_true", help=argparse.SUPPRESS)
445 set_cdb_authentication_token(db, args.auth_token)
447 return change_state(db, args.tag, args.state, args.force)
450 def remove_repeated_values(table, columns, keep=None):
451 """Strip repeated values from a table of values
453 This function takes a table (a list of lists with all the same length) and
454 removes values in certain columns if they are identical in consecutive rows.
456 It does this in a dependent way only if the previous columns are identical
457 will it continue stripping further columns. For example, given the table ::
466 If we want to remove duplicates in all columns in order it would look like this:
468 >>> remove_repeated_values(table, [0,1])
476 But we can give selected columns to strip one after the other
478 >>> remove_repeated_values(table, [1,0])
486 In addition, we might want to only strip some columns if previous columns
487 were identical but keep the values of the previous column. For this one can
490 >>> remove_repeated_values(table, [0,1,2], keep=[0])
499 table (list(list(str))): 2d table of values
500 columns (list(int)): indices of columns to consider in order
501 keep (set(int)): indices of columns to not strip
503 last_values = [
None] * len(columns)
504 for row
in table[1:]:
505 current_values = [row[i]
for i
in columns]
506 for i, curr, last
in zip(columns, current_values, last_values):
510 if keep
and i
in keep:
515 last_values = current_values
518 def command_diff(args, db):
519 """Compare two globaltags
521 This command allows to compare two globaltags. It will show the changes in
522 a format similar to a unified diff but by default it will not show any
523 context, only the new or removed payloads. Added payloads are marked with a
524 ``+`` in the first column, removed payloads with a ``-``
526 If ``--full`` is given it will show all payloads, even the ones common to
527 both globaltags. The differences can be limited to a given run and
528 limited to a set of payloads names using ``--filter`` or ``--exclude``. If
529 the ``--regex`` option is supplied the search term will be interpreted as a
530 python regular expression where the case is ignored.
532 .. versionchanged:: release-03-00-00
533 modified output structure and added ``--human-readable``
534 .. versionchanged:: after release-04-00-00
535 added parameter ``--checksums`` and ``--show-ids``
537 iovfilter = ItemFilter(args)
539 args.add_argument(
"--full", default=
False, action=
"store_true",
540 help=
"If given print all iovs, also those which are the same in both tags")
541 args.add_argument(
"--run", type=int, nargs=2, metavar=
"N", help=
"exp and run numbers "
542 "to limit showing iovs to a ones present in a given run")
543 args.add_argument(
"--human-readable", default=
False, action=
"store_true",
544 help=
"If given the iovs will be written in a more human friendly format. "
545 "Also repeated payload names will be omitted to create a more readable listing.")
546 args.add_argument(
"--checksums", default=
False, action=
"store_true",
547 help=
"If given don't show the revision number but the md5 checksum")
548 args.add_argument(
"--show-ids", default=
False, action=
"store_true",
549 help=
"If given also show the payload and iov ids for each iov")
551 args.add_argument(
"tagA", metavar=
"TAGNAME1", help=
"base for comparison")
552 args.add_argument(
"tagB", metavar=
"TAGNAME2", help=
"tagname to compare")
553 iovfilter.add_arguments(
"payloads")
557 if not iovfilter.check_arguments():
560 with Pager(f
"Differences between globaltags {args.tagA} and {args.tagB}{iovfilter}",
True):
561 print(
"globaltags to be compared:")
562 ntags = print_globaltag(db, args.tagA, args.tagB)
566 listA = [e
for e
in db.get_all_iovs(args.tagA, message=str(iovfilter))
if iovfilter.check(e.name)]
567 listB = [e
for e
in db.get_all_iovs(args.tagB, message=str(iovfilter))
if iovfilter.check(e.name)]
569 B2INFO(
"Comparing contents ...")
570 diff = difflib.SequenceMatcher(a=listA, b=listB)
571 table = [[
"",
"Name",
"Rev" if not args.checksums
else "Checksum"]]
572 columns = [1,
"+", -8
if not args.checksums
else -32]
574 if args.human_readable:
578 table[0] += [
"First Exp",
"First Run",
"Final Exp",
"Final Run"]
579 columns += [6, 6, 6, 6]
582 table[0] += [
"IovId",
"PayloadId"]
585 def add_payloads(opcode, payloads):
586 """Add a list of payloads to the table, filling the first column with opcode"""
588 row = [opcode, p.name, p.revision
if not args.checksums
else p.checksum]
589 if args.human_readable:
590 row += [p.readable_iov()]
595 row += [p.iov_id, p.payload_id]
598 for tag, i1, i2, j1, j2
in diff.get_opcodes():
602 add_payloads(
" ", listB[j1:j2])
603 if tag
in [
"delete",
"replace"]:
604 add_payloads(
"-", listA[i1:i2])
605 if tag
in [
"insert",
"replace"]:
606 add_payloads(
"+", listB[j1:j2])
608 if args.human_readable:
612 remove_repeated_values(table, [0, 1, 2] + ([-1]
if args.show_ids
else []), keep=[0])
614 def color_row(row, widths, line):
615 if not LogPythonInterface.terminal_supports_colors():
617 begin = {
'+':
'\x1b[32m',
'-':
'\x1b[31m'}.get(row[0],
"")
619 return begin + line + end
624 print(f
" Differences between {args.tagA} and {args.tagB}")
625 pretty_print_table(table, columns, transform=color_row,
626 hline_formatter=
lambda w:
" " + (w - 1) *
'-')
629 def command_iov(args, db):
631 List all IoVs defined in a globaltag, optionally limited to a run range
633 This command lists all IoVs defined in a given globaltag. The list can be
634 limited to a given run and optionally searched using --filter or --exclude.
635 If the --regex option is supplied the search term will be interpreted as a
636 python regular expression where the case is ignored.
638 .. versionchanged:: release-03-00-00
639 modified output structure and added ``--human-readable``
640 .. versionchanged:: after release-04-00-00
641 added parameter ``--checksums`` and ``--show-ids``
644 iovfilter = ItemFilter(args)
647 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag for which the the IoVs should be listed")
648 args.add_argument(
"--run", type=int, nargs=2, metavar=
"N", help=
"exp and run numbers "
649 "to limit showing iovs to a ones present in a given run")
650 args.add_argument(
"--detail", action=
"store_true", default=
False,
651 help=
"if given show a detailed information for all "
652 "IoVs including details of the payloads")
653 args.add_argument(
"--human-readable", default=
False, action=
"store_true",
654 help=
"If given the iovs will be written in a more human friendly format. "
655 "Also repeated payload names will be omitted to create a more readable listing.")
656 args.add_argument(
"--checksums", default=
False, action=
"store_true",
657 help=
"If given don't show the revision number but the md5 checksum")
658 args.add_argument(
"--show-ids", default=
False, action=
"store_true",
659 help=
"If given also show the payload and iov ids for each iov")
660 iovfilter.add_arguments(
"payloads")
664 if not iovfilter.check_arguments():
667 if args.run
is not None:
668 msg =
"Obtaining list of iovs for globaltag {tag}, exp={exp}, run={run}{filter}".format(
669 tag=args.tag, exp=args.run[0], run=args.run[1], filter=iovfilter)
670 req = db.request(
"GET",
"/iovPayloads", msg, params={
'gtName': args.tag,
'expNumber': args.run[0],
671 'runNumber': args.run[1]})
673 msg = f
"Obtaining list of iovs for globaltag {args.tag}{iovfilter}"
674 req = db.request(
"GET",
"/globalTag/{}/globalTagPayloads".format(encode_name(args.tag)), msg)
676 with Pager(
"List of IoVs{}{}".format(iovfilter,
" (detailed)" if args.detail
else ""),
True):
678 for item
in req.json():
679 payload = item[
"payload" if 'payload' in item
else "payloadId"]
680 if "payloadIov" in item:
681 iovs = [item[
'payloadIov']]
683 iovs = item[
'payloadIovs']
685 if not iovfilter.check(payload[
'basf2Module'][
'name']):
692 iov_created = parse_date(iov[
"dtmIns"])
693 iov_modified = parse_date(iov[
"dtmMod"])
694 payload_created = parse_date(payload[
"dtmIns"])
695 payload_modified = parse_date(payload[
"dtmMod"])
697 [
"IoV Id", str(iov[
"payloadIovId"])],
698 [
"first experiment", iov[
"expStart"]],
699 [
"first run", iov[
"runStart"]],
700 [
"final experiment", iov[
"expEnd"]],
701 [
"final run", iov[
"runEnd"]],
702 [
"IoV created", iov_created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
703 [
"IoV modified", iov_modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
704 [
"IoV modified by", iov[
"modifiedBy"]],
705 [
"payload Id", str(payload[
"payloadId"])],
706 [
"name", payload[
"basf2Module"][
"name"]],
707 [
"revision", payload[
"revision"]],
708 [
"checksum", payload[
"checksum"]],
709 [
"payloadUrl", payload[
"payloadUrl"]],
710 [
"baseUrl", payload.get(
"baseUrl",
"")],
712 [
"payload created", payload_created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
713 [
"payload modified", payload_modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
714 [
"payload modified by", escape_ctrl_chars(payload[
"modifiedBy"])],
717 pretty_print_table(result, [-40,
'*'],
True)
719 payloads.append(PayloadInformation.from_json(payload, iov))
722 def add_ids(table, columns, payloads):
723 """Add the numerical ids to the table"""
725 table[0] += [
"IovId",
"PayloadId"]
727 for row, p
in zip(table[1:], payloads):
728 row += [p.iov_id, p.payload_id]
730 if args.human_readable:
731 table = [[
"Name",
"Rev" if not args.checksums
else "Checksum",
"IoV"]]
732 columns = [
"+", -8
if not args.checksums
else -32, -32]
733 table += [[p.name, p.revision
if not args.checksums
else p.checksum, p.readable_iov()]
for p
in payloads]
734 add_ids(table, columns, payloads)
736 remove_repeated_values(table, columns=[0, 1] + ([-1]
if args.show_ids
else []))
739 table = [[
"Name",
"Rev" if not args.checksums
else "Checksum",
"First Exp",
"First Run",
"Final Exp",
"Final Run"]]
740 table += [[p.name, p.revision
if not args.checksums
else p.checksum] + list(p.iov)
for p
in payloads]
741 columns = [
"+", -8
if not args.checksums
else -32, 6, 6, 6, 6]
742 add_ids(table, columns, payloads)
744 pretty_print_table(table, columns)
747 def command_dump(args, db):
749 Dump the content of a given payload
751 .. versionadded:: release-03-00-00
753 This command will dump the payload contents stored in a given payload. One
754 can either specify the payloadId (from a previous output of
755 ``b2conditionsdb iov``), the payload name and its revision in the central
756 database, or directly specify a local database payload file.
760 Dump the content of a previously downloaded payload file:
762 $ b2conditionsdb dump -f centraldb/dbstore_BeamParameters_rev_59449.root
764 Dump the content of a payload by name and revision directly from the central database:
766 $ b2conditionsdb dump -r BeamParameters 59449
768 Dump the content of the payload by name which is valid in a given globaltag
769 for a given experiment and run::
771 $ b2conditionsdb dump -g BeamParameters main_2021-08-04 0 0
773 Or directly by payload id from a previous call to ``b2conditionsdb iov``:
775 $ b2conditionsdb dump -i 59685
779 Depending on whether you want to display a payload by its id in the
780 database, its name and revision in the database or from a local file
781 provide **one** of the arguments ``-i``, ``-r``, ``-f`` or ``-g``
783 .. versionchanged:: after release-04-00-00
784 added argument ``-r`` to directly dump a payload valid for a given run
788 group = args.add_mutually_exclusive_group(required=
True)
789 choice = group.add_mutually_exclusive_group()
790 choice.add_argument(
"-i",
"--id", metavar=
"PAYLOADID", help=
"payload id to dump")
791 choice.add_argument(
"-r",
"--revision", metavar=(
"NAME",
"REVISION"), nargs=2,
792 help=
"Name and revision of the payload to dump")
793 choice.add_argument(
"-f",
"--file", metavar=
"FILENAME", help=
"Dump local payload file")
794 choice.add_argument(
"-g",
"--valid", metavar=(
"NAME",
"GLOBALTAG",
"EXP",
"RUN"), nargs=4,
795 help=
"Dump the payload valid for the given exp, run number in the given globaltag")
796 args.add_argument(
"--show-typenames", default=
False, action=
"store_true",
797 help=
"If given show the type names of all classes. "
798 "This makes output more crowded but can be helpful for complex objects.")
799 args.add_argument(
"--show-streamerinfo", default=
False, action=
"store_true",
800 help=
"If given show the StreamerInfo for the classes in the the payload file. "
801 "This can be helpful to find out which version of a payload object "
802 "is included and what are the members")
810 if not os.path.isfile(filename):
811 B2ERROR(f
"Payloadfile {filename} could not be found")
814 match = re.match(
r"^dbstore_(.*)_rev_(.*).root$", os.path.basename(filename))
816 match = re.match(
r"^(.*)_r(.*).root$", os.path.basename(filename))
818 B2ERROR(
"Filename doesn't follow database convention.\n"
819 "Should be 'dbstore_${payloadname}_rev_${revision}.root' or '${payloadname}_r${revision.root}'")
821 name = match.group(1)
822 revision = match.group(2)
823 payloadId =
"Unknown"
828 req = db.request(
"GET", f
"/payload/{args.id}",
"Getting payload info")
829 payload = PayloadInformation.from_json(req.json())
832 name, rev = args.revision
834 req = db.request(
"GET", f
"/module/{encode_name(name)}/payloads",
"Getting payload info")
836 if p[
"revision"] == rev:
837 payload = PayloadInformation.from_json(p)
840 B2ERROR(f
"Cannot find payload {name} with revision {rev}")
843 name, globaltag, exp, run = args.valid
845 for p
in db.get_all_iovs(globaltag, exp, run, f
", name={name}"):
846 if p.name == name
and (payload
is None or p.revision > payload.revision):
850 B2ERROR(f
"Cannot find payload {name} in globaltag {globaltag} for exp,run {exp},{run}")
853 filename = payload.url
854 revision = payload.revision
855 payloadId = payload.payload_id
859 from ROOT
import TFile, TBufferJSON, cout
862 tfile = TFile.Open(filename)
865 if not tfile
or not tfile.IsOpen():
867 contents = db._session.get(filename, stream=
True)
868 if contents.status_code != requests.codes.ok:
869 B2ERROR(f
"Could not open payload file {filename}")
871 raw_contents = contents.raw.read().decode()
873 obj = tfile.Get(name)
875 json_str = TBufferJSON.ConvertToJSON(obj)
879 Drop some members from ROOT json output.
881 We do not care about fBits, fUniqueID or the typename of sub classes,
882 we assume users are only interested in the data stored in the member
885 obj.pop(
"fBits",
None)
886 obj.pop(
"fUniqueID",
None)
887 if not args.show_typenames:
888 obj.pop(
"_typename",
None)
891 with Pager(f
"Contents of Payload {name}, revision {revision} (id {payloadId})",
True):
892 if args.show_streamerinfo
and tfile:
893 B2INFO(
"StreamerInfo of Payload {name}, revision {revision} (id {payloadId})")
894 tfile.ShowStreamerInfo()
901 if json_str
is not None:
902 B2INFO(f
"Contents of Payload {name}, revision {revision} (id {payloadId})")
905 obj = json.loads(json_str.Data(), object_hook=drop_fbits)
907 pprint.pprint(obj, compact=
True, width=shutil.get_terminal_size((80, 20))[0])
909 B2INFO(f
"Raw contents of Payload {name}, revision {revision} (id {payloadId})")
910 print(escape_ctrl_chars(raw_contents))
912 B2INFO(f
"ROOT contents of Payload {name}, revision {revision} (id {payloadId})")
913 B2WARNING(
"The payload is a valid ROOT file but doesn't contain a payload object with the expected name. "
914 " Automated display of file contents are not possible, showing just entries in the ROOT file.")
919 """Class to recursively show help for an ArgumentParser and all it's sub_parsers"""
922 """Print help message for given parser and call again for all sub parsers"""
924 subparsers_actions = [
925 action
for action
in parser._actions
926 if isinstance(action, argparse._SubParsersAction)]
929 for subparsers_action
in subparsers_actions:
931 for choice, subparser
in subparsers_action.choices.items():
933 print(f
"Command '{prefix}{choice}'")
934 print(subparser.format_help())
936 self.
print_subparsersprint_subparsers(subparser, prefix=f
"{prefix}{choice} ")
938 def __call__(self, parser, namespace, values, option_string=None):
939 """Show full help message"""
941 with Pager(f
"{parser.prog} {option_string}"):
947 def get_argument_parser():
949 Build a parser with all arguments of all commands
952 options = argparse.ArgumentParser(add_help=
False)
953 options.add_argument(
"--debugging", action=
"store_true",
954 help=
"Enable debugging of http traffic")
955 options.add_argument(
"--help-full", action=FullHelpAction,
956 help=
"show help message for all commands and exit")
957 options.add_argument(
"--base-url", default=
None,
958 help=
"URI for the base of the REST API, if not given a list of default locations is tried")
959 options.add_argument(
"--auth-token", type=str, default=
None,
960 help=
"JSON Web Token necessary for authenticating to the conditions database. "
961 "Useful only for debugging, since by default the tool automatically "
962 "gets a token for you by asking the B2MMS username and password. "
963 "If the environment variable ``$BELLE2_CDB_AUTH_TOKEN`` points to a file with a valid "
964 "token, such token is used (useful for automatic workflows).")
966 parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, parents=[options])
967 parser.set_defaults(func=
lambda x, y: parser.print_help())
968 parsers = parser.add_subparsers(
969 title=
"Top level commands",
970 description=
"To get additional help, run '%(prog)s COMMAND --help'"
975 for name, func
in sorted(globals().items()):
976 if not name.startswith(
"command_"):
981 parts = name.split(
'_')[1:]
987 parent_parser, parent = subparsers[tuple(parts[:-1])]
991 parent = parent_parser.add_subparsers(
992 title=
"sub commands",
993 description=
"To get additional help, run '%(prog)s COMMAND --help'"
995 subparsers[tuple(parts[:-1])][1] = parent
1000 helptxt, description = textwrap.dedent(func.__doc__).split(
"\n\n", 1)
1001 command_parser = parent.add_parser(parts[-1], help=helptxt, add_help=
True, description=description,
1002 parents=[options], formatter_class=argparse.RawDescriptionHelpFormatter)
1005 func(command_parser,
None)
1007 command_parser.set_defaults(func=func)
1009 subparsers[tuple(parts)] = [command_parser,
None]
1014 def create_symlinks(base):
1015 """Create symlinks from base to all subcommands.
1017 e.g. if the base is ``b2conditionsdb`` then this command will create symlinks
1018 like ``b2conditionsdb-tag-show`` in the same directory
1020 When adding a new command to b2conditionsdb this function needs to be executed
1021 in the framework tools directory
1023 python3 -c 'from conditions_db import cli_main; cli_main.create_symlinks("b2conditionsdb")'
1029 for name
in sorted(globals().keys()):
1030 if not name.startswith(
"command_"):
1032 parts = name.split(
"_")[1:]
1033 if parts
in excluded:
1035 dest = base +
"-".join([
""] + parts)
1039 except FileNotFoundError:
1041 print(f
"create symlink {dest}")
1042 os.symlink(base, dest)
1047 Main function for the command line interface.
1049 it will automatically create an ArgumentParser including all functions which
1050 start with command_ in the global namespace as sub commands. These
1051 functions should take the arguments as first argument and an instance of the
1052 ConditionsDB interface as second argument. If the db interface is None the
1053 first argument is an instance of argparse.ArgumentParser an in this case the
1054 function should just add all needed arguments to the argument parser and
1059 logging.enable_summary(
False)
1061 logging.enable_python_logging =
True
1063 for level
in LogLevel.values.values():
1064 logging.set_info(level, LogInfo.LEVEL | LogInfo.MESSAGE)
1068 sys.argv[0:1] = os.path.basename(sys.argv[0]).split(
'-')
1071 parser = get_argument_parser()
1075 args = parser.parse_args()
1082 nprocess = getattr(args,
"nprocess", 1)
1083 retries = getattr(args,
"retries", 0)
1086 B2WARNING(
"-j must be larger than zero, ignoring")
1087 args.nprocess = nprocess = 1
1089 conditions_db =
ConditionsDB(args.base_url, nprocess, retries)
1092 return args.func(args, conditions_db)
def print_subparsers(self, parser, prefix="")
def __call__(self, parser, namespace, values, option_string=None)
int main(int argc, char **argv)
Run all tests.