12 This script provides a command line interface to all the tasks related to the
13 :ref:`Conditions database <conditionsdb_overview>`: manage globaltags and iovs as well as upload new payloads
14 or download of existing payloads.
16 The usage of this program is similar to git: there are sub commands like for
17 example ``tag`` which groups all actions related to the management of global
18 tags. All the available commands are listed below.
42 from basf2
import B2ERROR, B2WARNING, B2INFO, LogLevel, LogInfo, logging, \
45 from terminal_utils
import Pager
46 from dateutil.parser
import parse
as parse_date
47 from getpass
import getuser
48 from conditions_db
import ConditionsDB, enable_debugging, encode_name, PayloadInformation
57 def escape_ctrl_chars(name):
58 """Remove ANSI control characters from strings"""
60 if not hasattr(escape_ctrl_chars,
"_regex"):
61 escape_ctrl_chars._regex = re.compile(
"[\x00-\x1f\x7f-\x9f]")
65 if match.group(0).isspace():
67 return "\\x{:02x}".format(ord(match.group(0)))
69 return escape_ctrl_chars._regex.sub(escape, name)
72 def command_tag(args, db=None):
74 List, show, create, modify or clone globaltags.
76 This command allows to list, show, create modify or clone globaltags in the
77 central database. If no other command is given it will list all tags as if
78 "%(prog)s show" was given.
84 command_tag_list(args, db)
87 def command_tag_list(args, db=None):
89 List all available globaltags.
91 This command allows to list all globaltags, optionally limiting the output
92 to ones matching a given search term. By default invalidated globaltags
93 will not be included in the list, to show them as well please add
94 --with-invalid as option. Alternatively one can use --only-published to show
95 only tags which have been published
97 If the --regex option is supplied the search term will be interpreted as a
98 python regular expression where the case is ignored.
101 tagfilter = ItemFilter(args)
104 args.add_argument(
"--detail", action=
"store_true", default=
False,
105 help=
"show detailed information for all tags instead "
107 group = args.add_mutually_exclusive_group()
108 group.add_argument(
"--with-invalid", action=
"store_true", default=
False,
109 help=
"if given also invalidated tags will be shown")
110 group.add_argument(
"--only-published", action=
"store_true", default=
False,
111 help=
"if given only published tags will be shown")
112 tagfilter.add_arguments(
"tags")
116 if not tagfilter.check_arguments():
119 req = db.request(
"GET",
"/globalTags", f
"Getting list of globaltags{tagfilter}")
123 for item
in req.json():
124 if not tagfilter.check(item[
"name"]):
126 tagstatus = item[
"globalTagStatus"][
"name"]
127 if not getattr(args,
"with_invalid",
False)
and tagstatus ==
"INVALID":
129 if getattr(args,
"only_published",
False)
and tagstatus !=
"PUBLISHED":
134 taglist.sort(key=
lambda x: x[
"name"])
138 with Pager(
"List of globaltags{}{}".format(tagfilter,
" (detailed)" if getattr(args,
"detail",
False)
else ""),
True):
140 if getattr(args,
"detail",
False):
141 print_globaltag(db, item)
146 escape_ctrl_chars(item.get(
"description",
"")),
147 item[
"globalTagType"][
"name"],
148 item[
"globalTagStatus"][
"name"],
149 item[
"payloadCount"],
152 if not getattr(args,
"detail",
False):
153 table.insert(0, [
"id",
"name",
"description",
"type",
"status",
"# payloads"])
154 pretty_print_table(table, [-10, 0,
"*", -10, -10, -10], min_flexible_width=32)
157 def print_globaltag(db, *tags):
158 """ Print detailed globaltag information for the given globaltags side by side"""
159 results = [[
"id"], [
"name"], [
"description"], [
"type"], [
"status"],
160 [
"# payloads"], [
"created"], [
"modified"], [
"modified by"]]
165 if isinstance(info, str):
167 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(info)),
168 f
"Getting info for globaltag {info}")
169 except ConditionsDB.RequestError
as e:
177 created = parse_date(info[
"dtmIns"])
178 modified = parse_date(info[
"dtmMod"])
179 results[0].append(str(info[
"globalTagId"])),
180 results[1].append(info[
"name"]),
181 results[2].append(escape_ctrl_chars(info.get(
"description",
"")))
182 results[3].append(info[
"globalTagType"][
"name"])
183 results[4].append(info[
"globalTagStatus"][
"name"]),
184 results[5].append(info[
"payloadCount"]),
185 results[6].append(created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time"))
186 results[7].append(modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time"))
187 results[8].append(escape_ctrl_chars(info[
"modifiedBy"]))
189 ntags = len(results[0]) - 1
191 pretty_print_table(results, [11] + [
'*'] * ntags,
True)
195 def change_state(db, tag, state, force=False):
196 """Change the state of a global tag
198 If the new state is not revertible then ask for confirmation
200 state = state.upper()
201 if state
in [
"INVALID",
"PUBLISHED"]
and not force:
202 name = input(f
"ATTENTION: Marking a tag as {state} cannot be undone.\n"
203 "If you are sure you want to continue it please enter the tag name again: ")
205 B2ERROR(
"Names don't match, aborting")
208 db.request(
"PUT", f
"/globalTag/{encode_name(tag)}/updateGlobalTagStatus/{state}",
209 f
"Changing globaltag state {tag} to {state}")
212 def command_tag_show(args, db=None):
214 Show details about globaltags
216 This command will show details for the given globaltags like name,
217 description and number of payloads.
225 args.add_argument(
"tag", metavar=
"TAGNAME", nargs=
"+", help=
"globaltags to show")
230 with Pager(
"globaltag Information",
True):
232 ntags += print_globaltag(db, tag)
235 return len(args.tag) - ntags
238 def command_tag_create(args, db=None):
240 Create a new globaltag
242 This command creates a new globaltag in the database with the given name
243 and description. The name can only contain alpha-numeric characters and the
248 args.add_argument(
"type", metavar=
"TYPE", help=
"type of the globaltag to create, usually one of DEV or RELEASE")
249 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"name of the globaltag to create")
250 args.add_argument(
"description", metavar=
"DESCRIPTION", help=
"description of the globaltag")
251 args.add_argument(
"-u",
"--user", metavar=
"USER", help=
"username who created the tag. "
252 "If not given we will try to supply a useful default")
256 info = {
"name": args.tag,
"description": args.description,
"modifiedBy": args.user,
"isDefault":
False}
258 if args.user
is None:
259 info[
"modifiedBy"] = os.environ.get(
"BELLE2_USER", getuser())
261 typeinfo = db.get_globalTagType(args.type)
265 req = db.request(
"POST",
"/globalTag/{}".format(encode_name(typeinfo[
"name"])),
266 "Creating globaltag {name}".format(**info),
268 B2INFO(
"Successfully created globaltag {name} (id={globalTagId})".format(**req.json()))
271 def command_tag_modify(args, db=None):
273 Modify a globaltag by changing name or description
275 This command allows to change the name or description of an existing globaltag.
276 You can supply any combination of -n,-d,-t and only the given values will be changed
279 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to modify")
280 args.add_argument(
"-n",
"--name", help=
"new name")
281 args.add_argument(
"-d",
"--description", help=
"new description")
282 args.add_argument(
"-t",
"--type", help=
"new type of the globaltag")
283 args.add_argument(
"-u",
"--user", metavar=
"USER", help=
"username who created the tag. "
284 "If not given we will try to supply a useful default")
285 args.add_argument(
"-s",
"--state", help=
"new globaltag state, see the command ``tag state`` for details")
289 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(args.tag)),
290 f
"Getting info for globaltag {args.tag}")
294 old_name = info[
"name"]
296 for key
in [
"name",
"description"]:
297 value = getattr(args, key)
298 if value
is not None and value != info[key]:
302 info[
"modifiedBy"] = os.environ.get(
"BELLE2_USER", os.getlogin())
if args.user
is None else args.user
304 if args.type
is not None:
306 typeinfo = db.get_globalTagType(args.type)
310 if info[
'gloalTagType'] != typeinfo:
311 info[
"globalTagType"] = typeinfo
316 db.request(
"PUT",
"/globalTag",
317 "Modifying globaltag {} (id={globalTagId})".format(old_name, **info),
320 if args.state
is not None:
321 name = args.name
if args.name
is not None else old_name
322 return change_state(db, name, args.state)
325 def command_tag_clone(args, db=None):
327 Clone a given globaltag including all IoVs
329 This command allows to clone a given globaltag with a new name but still
330 containing all the IoVs defined in the original globaltag.
334 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to be cloned")
335 args.add_argument(
"name", metavar=
"NEWNAME", help=
"name of the cloned globaltag")
339 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(args.tag)),
340 f
"Getting info for globaltag {args.tag}")
344 req = db.request(
"POST",
"/globalTags/{globalTagId}".format(**info),
345 "Cloning globaltag {name} (id={globalTagId})".format(**info))
349 cloned_info = req.json()
350 cloned_info[
"name"] = args.name
352 db.request(
"PUT",
"/globalTag",
"Renaming globaltag {name} (id={globalTagId})".format(**cloned_info),
356 def command_tag_state(args, db):
358 Change the state of a globaltag.
360 This command changes the state of a globaltag to the given value.
362 Usually the valid states are
365 Tag can be modified, payloads and iovs can be created and deleted. This
366 is the default state for new or cloned globaltags and is not suitable
367 for use in data analysis
369 Can be transitioned to TESTING, RUNNING
372 Tag cannot be modified and is suitable for testing but can be reopened
374 Can be transitioned to VALIDATED, OPEN
377 Tag cannot be modified and has been tested.
379 Can be transitioned to PUBLISHED, OPEN
382 Tag cannot be modified and is suitable for user analysis
384 Can only be transitioned to INVALID
387 Tag can only be modified by adding new runs, not modifying the payloads
391 Tag is invalid and should not be used for anything.
393 This state is end of life for a globaltag and cannot be transitioned to
396 .. versionadded:: release-04-00-00
399 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to be changed")
400 args.add_argument(
"state", metavar=
"STATE", help=
"new state for the globaltag")
401 args.add_argument(
"--force", default=
False, action=
"store_true", help=argparse.SUPPRESS)
404 return change_state(db, args.tag, args.state, args.force)
407 def remove_repeated_values(table, columns, keep=None):
408 """Strip repeated values from a table of values
410 This function takes a table (a list of lists with all the same length) and
411 removes values in certain columns if they are identical in consecutive rows.
413 It does this in a dependent way only if the previous columns are identical
414 will it continue stripping further columns. For example, given the table ::
423 If we want to remove duplicates in all columns in order it would look like this:
425 >>> remove_repeated_values(table, [0,1])
433 But we can give selected columns to strip one after the other
435 >>> remove_repeated_values(table, [1,0])
443 In addition, we might want to only strip some columns if previous columns
444 were identical but keep the values of the previous column. For this one can
447 >>> remove_repeated_values(table, [0,1,2], keep=[0])
456 table (list(list(str))): 2d table of values
457 columns (list(int)): indices of columns to consider in order
458 keep (set(int)): indices of columns to not strip
460 last_values = [
None] * len(columns)
461 for row
in table[1:]:
462 current_values = [row[i]
for i
in columns]
463 for i, curr, last
in zip(columns, current_values, last_values):
467 if keep
and i
in keep:
472 last_values = current_values
475 def command_diff(args, db):
476 """Compare two globaltags
478 This command allows to compare two globaltags. It will show the changes in
479 a format similar to a unified diff but by default it will not show any
480 context, only the new or removed payloads. Added payloads are marked with a
481 ``+`` in the first column, removed payloads with a ``-``
483 If ``--full`` is given it will show all payloads, even the ones common to
484 both globaltags. The differences can be limited to a given run and
485 limited to a set of payloads names using ``--filter`` or ``--exclude``. If
486 the ``--regex`` option is supplied the search term will be interpreted as a
487 python regular expression where the case is ignored.
489 .. versionchanged:: release-03-00-00
490 modified output structure and added ``--human-readable``
491 .. versionchanged:: after release-04-00-00
492 added parameter ``--checksums`` and ``--show-ids``
494 iovfilter = ItemFilter(args)
496 args.add_argument(
"--full", default=
False, action=
"store_true",
497 help=
"If given print all iovs, also those which are the same in both tags")
498 args.add_argument(
"--run", type=int, nargs=2, metavar=
"N", help=
"exp and run numbers "
499 "to limit showing iovs to a ones present in a given run")
500 args.add_argument(
"--human-readable", default=
False, action=
"store_true",
501 help=
"If given the iovs will be written in a more human friendly format. "
502 "Also repeated payload names will be omitted to create a more readable listing.")
503 args.add_argument(
"--checksums", default=
False, action=
"store_true",
504 help=
"If given don't show the revision number but the md5 checksum")
505 args.add_argument(
"--show-ids", default=
False, action=
"store_true",
506 help=
"If given also show the payload and iov ids for each iov")
508 args.add_argument(
"tagA", metavar=
"TAGNAME1", help=
"base for comparison")
509 args.add_argument(
"tagB", metavar=
"TAGNAME2", help=
"tagname to compare")
510 iovfilter.add_arguments(
"payloads")
514 if not iovfilter.check_arguments():
517 with Pager(f
"Differences between globaltags {args.tagA} and {args.tagB}{iovfilter}",
True):
518 print(
"globaltags to be compared:")
519 ntags = print_globaltag(db, args.tagA, args.tagB)
523 listA = [e
for e
in db.get_all_iovs(args.tagA, message=str(iovfilter))
if iovfilter.check(e.name)]
524 listB = [e
for e
in db.get_all_iovs(args.tagB, message=str(iovfilter))
if iovfilter.check(e.name)]
526 B2INFO(
"Comparing contents ...")
527 diff = difflib.SequenceMatcher(a=listA, b=listB)
528 table = [[
"",
"Name",
"Rev" if not args.checksums
else "Checksum"]]
529 columns = [1,
"+", -8
if not args.checksums
else -32]
531 if args.human_readable:
535 table[0] += [
"First Exp",
"First Run",
"Final Exp",
"Final Run"]
536 columns += [6, 6, 6, 6]
539 table[0] += [
"IovId",
"PayloadId"]
542 def add_payloads(opcode, payloads):
543 """Add a list of payloads to the table, filling the first column with opcode"""
545 row = [opcode, p.name, p.revision
if not args.checksums
else p.checksum]
546 if args.human_readable:
547 row += [p.readable_iov()]
552 row += [p.iov_id, p.payload_id]
555 for tag, i1, i2, j1, j2
in diff.get_opcodes():
559 add_payloads(
" ", listB[j1:j2])
560 if tag
in [
"delete",
"replace"]:
561 add_payloads(
"-", listA[i1:i2])
562 if tag
in [
"insert",
"replace"]:
563 add_payloads(
"+", listB[j1:j2])
565 if args.human_readable:
569 remove_repeated_values(table, [0, 1, 2] + ([-1]
if args.show_ids
else []), keep=[0])
571 def color_row(row, widths, line):
572 if not LogPythonInterface.terminal_supports_colors():
574 begin = {
'+':
'\x1b[32m',
'-':
'\x1b[31m'}.get(row[0],
"")
576 return begin + line + end
581 print(f
" Differences between {args.tagA} and {args.tagB}")
582 pretty_print_table(table, columns, transform=color_row,
583 hline_formatter=
lambda w:
" " + (w - 1) *
'-')
586 def command_iov(args, db):
588 List all IoVs defined in a globaltag, optionally limited to a run range
590 This command lists all IoVs defined in a given globaltag. The list can be
591 limited to a given run and optionally searched using --filter or --exclude.
592 If the --regex option is supplied the search term will be interpreted as a
593 python regular expression where the case is ignored.
595 .. versionchanged:: release-03-00-00
596 modified output structure and added ``--human-readable``
597 .. versionchanged:: after release-04-00-00
598 added parameter ``--checksums`` and ``--show-ids``
601 iovfilter = ItemFilter(args)
604 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag for which the the IoVs should be listed")
605 args.add_argument(
"--run", type=int, nargs=2, metavar=
"N", help=
"exp and run numbers "
606 "to limit showing iovs to a ones present in a given run")
607 args.add_argument(
"--detail", action=
"store_true", default=
False,
608 help=
"if given show a detailed information for all "
609 "IoVs including details of the payloads")
610 args.add_argument(
"--human-readable", default=
False, action=
"store_true",
611 help=
"If given the iovs will be written in a more human friendly format. "
612 "Also repeated payload names will be omitted to create a more readable listing.")
613 args.add_argument(
"--checksums", default=
False, action=
"store_true",
614 help=
"If given don't show the revision number but the md5 checksum")
615 args.add_argument(
"--show-ids", default=
False, action=
"store_true",
616 help=
"If given also show the payload and iov ids for each iov")
617 iovfilter.add_arguments(
"payloads")
621 if not iovfilter.check_arguments():
624 if args.run
is not None:
625 msg =
"Obtaining list of iovs for globaltag {tag}, exp={exp}, run={run}{filter}".format(
626 tag=args.tag, exp=args.run[0], run=args.run[1], filter=iovfilter)
627 req = db.request(
"GET",
"/iovPayloads", msg, params={
'gtName': args.tag,
'expNumber': args.run[0],
628 'runNumber': args.run[1]})
630 msg = f
"Obtaining list of iovs for globaltag {args.tag}{iovfilter}"
631 req = db.request(
"GET",
"/globalTag/{}/globalTagPayloads".format(encode_name(args.tag)), msg)
633 with Pager(
"List of IoVs{}{}".format(iovfilter,
" (detailed)" if args.detail
else ""),
True):
635 for item
in req.json():
636 payload = item[
"payload" if 'payload' in item
else "payloadId"]
637 if "payloadIov" in item:
638 iovs = [item[
'payloadIov']]
640 iovs = item[
'payloadIovs']
642 if not iovfilter.check(payload[
'basf2Module'][
'name']):
649 iov_created = parse_date(iov[
"dtmIns"])
650 iov_modified = parse_date(iov[
"dtmMod"])
651 payload_created = parse_date(payload[
"dtmIns"])
652 payload_modified = parse_date(payload[
"dtmMod"])
654 [
"IoV Id", str(iov[
"payloadIovId"])],
655 [
"first experiment", iov[
"expStart"]],
656 [
"first run", iov[
"runStart"]],
657 [
"final experiment", iov[
"expEnd"]],
658 [
"final run", iov[
"runEnd"]],
659 [
"IoV created", iov_created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
660 [
"IoV modified", iov_modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
661 [
"IoV modified by", iov[
"modifiedBy"]],
662 [
"payload Id", str(payload[
"payloadId"])],
663 [
"name", payload[
"basf2Module"][
"name"]],
664 [
"revision", payload[
"revision"]],
665 [
"checksum", payload[
"checksum"]],
666 [
"payloadUrl", payload[
"payloadUrl"]],
667 [
"baseUrl", payload.get(
"baseUrl",
"")],
669 [
"payload created", payload_created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
670 [
"payload modified", payload_modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
671 [
"payload modified by", escape_ctrl_chars(payload[
"modifiedBy"])],
674 pretty_print_table(result, [-40,
'*'],
True)
676 payloads.append(PayloadInformation.from_json(payload, iov))
679 def add_ids(table, columns, payloads):
680 """Add the numerical ids to the table"""
682 table[0] += [
"IovId",
"PayloadId"]
684 for row, p
in zip(table[1:], payloads):
685 row += [p.iov_id, p.payload_id]
687 if args.human_readable:
688 table = [[
"Name",
"Rev" if not args.checksums
else "Checksum",
"IoV"]]
689 columns = [
"+", -8
if not args.checksums
else -32, -32]
690 table += [[p.name, p.revision
if not args.checksums
else p.checksum, p.readable_iov()]
for p
in payloads]
691 add_ids(table, columns, payloads)
693 remove_repeated_values(table, columns=[0, 1] + ([-1]
if args.show_ids
else []))
696 table = [[
"Name",
"Rev" if not args.checksums
else "Checksum",
"First Exp",
"First Run",
"Final Exp",
"Final Run"]]
697 table += [[p.name, p.revision
if not args.checksums
else p.checksum] + list(p.iov)
for p
in payloads]
698 columns = [
"+", -8
if not args.checksums
else -32, 6, 6, 6, 6]
699 add_ids(table, columns, payloads)
701 pretty_print_table(table, columns)
704 def command_dump(args, db):
706 Dump the content of a given payload
708 .. versionadded:: release-03-00-00
710 This command will dump the payload contents stored in a given payload. One
711 can either specify the payloadId (from a previous output of
712 ``b2conditionsdb iov``), the payload name and its revision in the central
713 database, or directly specify a local database payload file.
717 Dump the content of a previously downloaded payload file:
719 $ b2conditionsdb dump -f centraldb/dbstore_BeamParameters_rev_59449.root
721 Dump the content of a payload by name and revision directly from the central database:
723 $ b2conditionsdb dump -r BeamParameters 59449
725 Dump the content of the payload by name which is valid in a given globaltag
726 for a given experiment and run::
728 $ b2conditionsdb dump -g BeamParameters main_2021-08-04 0 0
730 Or directly by payload id from a previous call to ``b2conditionsdb iov``:
732 $ b2conditionsdb dump -i 59685
736 Depending on whether you want to display a payload by its id in the
737 database, its name and revision in the database or from a local file
738 provide **one** of the arguments ``-i``, ``-r``, ``-f`` or ``-g``
740 .. versionchanged:: after release-04-00-00
741 added argument ``-r`` to directly dump a payload valid for a given run
745 group = args.add_mutually_exclusive_group(required=
True)
746 choice = group.add_mutually_exclusive_group()
747 choice.add_argument(
"-i",
"--id", metavar=
"PAYLOADID", help=
"payload id to dump")
748 choice.add_argument(
"-r",
"--revision", metavar=(
"NAME",
"REVISION"), nargs=2,
749 help=
"Name and revision of the payload to dump")
750 choice.add_argument(
"-f",
"--file", metavar=
"FILENAME", help=
"Dump local payload file")
751 choice.add_argument(
"-g",
"--valid", metavar=(
"NAME",
"GLOBALTAG",
"EXP",
"RUN"), nargs=4,
752 help=
"Dump the payload valid for the given exp, run number in the given globaltag")
753 args.add_argument(
"--show-typenames", default=
False, action=
"store_true",
754 help=
"If given show the type names of all classes. "
755 "This makes output more crowded but can be helpful for complex objects.")
756 args.add_argument(
"--show-streamerinfo", default=
False, action=
"store_true",
757 help=
"If given show the StreamerInfo for the classes in the the payload file. "
758 "This can be helpful to find out which version of a payload object "
759 "is included and what are the members")
767 if not os.path.isfile(filename):
768 B2ERROR(f
"Payloadfile {filename} could not be found")
771 match = re.match(
r"^dbstore_(.*)_rev_(.*).root$", os.path.basename(filename))
773 match = re.match(
r"^(.*)_r(.*).root$", os.path.basename(filename))
775 B2ERROR(
"Filename doesn't follow database convention.\n"
776 "Should be 'dbstore_${payloadname}_rev_${revision}.root' or '${payloadname}_r${revision.root}'")
778 name = match.group(1)
779 revision = match.group(2)
780 payloadId =
"Unknown"
785 req = db.request(
"GET", f
"/payload/{args.id}",
"Getting payload info")
786 payload = PayloadInformation.from_json(req.json())
789 name, rev = args.revision
791 req = db.request(
"GET", f
"/module/{encode_name(name)}/payloads",
"Getting payload info")
793 if p[
"revision"] == rev:
794 payload = PayloadInformation.from_json(p)
797 B2ERROR(f
"Cannot find payload {name} with revision {rev}")
800 name, globaltag, exp, run = args.valid
802 for p
in db.get_all_iovs(globaltag, exp, run, f
", name={name}"):
803 if p.name == name
and (payload
is None or p.revision > payload.revision):
807 B2ERROR(f
"Cannot find payload {name} in globaltag {globaltag} for exp,run {exp},{run}")
810 filename = payload.url
811 revision = payload.revision
812 payloadId = payload.payload_id
816 from ROOT
import TFile, TBufferJSON, cout
819 tfile = TFile.Open(filename)
822 if not tfile
or not tfile.IsOpen():
824 contents = db._session.get(filename, stream=
True)
825 if contents.status_code != requests.codes.ok:
826 B2ERROR(f
"Could not open payload file {filename}")
828 raw_contents = contents.raw.read().decode()
830 obj = tfile.Get(name)
832 json_str = TBufferJSON.ConvertToJSON(obj)
836 Drop some members from ROOT json output.
838 We do not care about fBits, fUniqueID or the typename of sub classes,
839 we assume users are only interested in the data stored in the member
842 obj.pop(
"fBits",
None)
843 obj.pop(
"fUniqueID",
None)
844 if not args.show_typenames:
845 obj.pop(
"_typename",
None)
848 with Pager(f
"Contents of Payload {name}, revision {revision} (id {payloadId})",
True):
849 if args.show_streamerinfo
and tfile:
850 B2INFO(
"StreamerInfo of Payload {name}, revision {revision} (id {payloadId})")
851 tfile.ShowStreamerInfo()
858 if json_str
is not None:
859 B2INFO(f
"Contents of Payload {name}, revision {revision} (id {payloadId})")
862 obj = json.loads(json_str.Data(), object_hook=drop_fbits)
864 pprint.pprint(obj, compact=
True, width=shutil.get_terminal_size((80, 20))[0])
866 B2INFO(f
"Raw contents of Payload {name}, revision {revision} (id {payloadId})")
867 print(escape_ctrl_chars(raw_contents))
869 B2INFO(f
"ROOT contents of Payload {name}, revision {revision} (id {payloadId})")
870 B2WARNING(
"The payload is a valid ROOT file but doesn't contain a payload object with the expected name. "
871 " Automated display of file contents are not possible, showing just entries in the ROOT file.")
876 """Class to recursively show help for an ArgumentParser and all it's sub_parsers"""
879 """Print help message for given parser and call again for all sub parsers"""
881 subparsers_actions = [
882 action
for action
in parser._actions
883 if isinstance(action, argparse._SubParsersAction)]
886 for subparsers_action
in subparsers_actions:
888 for choice, subparser
in subparsers_action.choices.items():
890 print(f
"Command '{prefix}{choice}'")
891 print(subparser.format_help())
893 self.
print_subparsersprint_subparsers(subparser, prefix=f
"{prefix}{choice} ")
895 def __call__(self, parser, namespace, values, option_string=None):
896 """Show full help message"""
898 with Pager(f
"{parser.prog} {option_string}"):
904 def get_argument_parser():
906 Build a parser with all arguments of all commands
909 options = argparse.ArgumentParser(add_help=
False)
910 options.add_argument(
"--debugging", action=
"store_true",
911 help=
"Enable debugging of http traffic")
912 options.add_argument(
"--help-full", action=FullHelpAction,
913 help=
"show help message for all commands and exit")
914 options.add_argument(
"--base-url", default=
None,
915 help=
"URI for the base of the REST API, if not given a list of default locations is tried")
916 options.add_argument(
"--http-auth", choices=[
"none",
"basic",
"digest"], default=
"basic",
917 help=argparse.SUPPRESS)
918 options.add_argument(
"--http-user", default=
"commonDBUser", help=argparse.SUPPRESS)
919 options.add_argument(
"--http-password", default=
"Eil9ohphoo2quot", help=argparse.SUPPRESS)
921 parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, parents=[options])
922 parser.set_defaults(func=
lambda x, y: parser.print_help())
923 parsers = parser.add_subparsers(
924 title=
"Top level commands",
925 description=
"To get additional help, run '%(prog)s COMMAND --help'"
930 for name, func
in sorted(globals().items()):
931 if not name.startswith(
"command_"):
936 parts = name.split(
'_')[1:]
942 parent_parser, parent = subparsers[tuple(parts[:-1])]
946 parent = parent_parser.add_subparsers(
947 title=
"sub commands",
948 description=
"To get additional help, run '%(prog)s COMMAND --help'"
950 subparsers[tuple(parts[:-1])][1] = parent
955 helptxt, description = textwrap.dedent(func.__doc__).split(
"\n\n", 1)
956 command_parser = parent.add_parser(parts[-1], help=helptxt, add_help=
True, description=description,
957 parents=[options], formatter_class=argparse.RawDescriptionHelpFormatter)
960 func(command_parser,
None)
962 command_parser.set_defaults(func=func)
964 subparsers[tuple(parts)] = [command_parser,
None]
969 def create_symlinks(base):
970 """Create symlinks from base to all subcommands.
972 e.g. if the base is ``b2conditionsdb`` then this command will create symlinks
973 like ``b2conditionsdb-tag-show`` in the same directory
975 When adding a new command to b2conditionsdb this function needs to be executed
976 in the framework tools directory
978 python3 -c 'from conditions_db import cli_main; cli_main.create_symlinks("b2conditionsdb")'
984 for name
in sorted(globals().keys()):
985 if not name.startswith(
"command_"):
987 parts = name.split(
"_")[1:]
988 if parts
in excluded:
990 dest = base +
"-".join([
""] + parts)
994 except FileNotFoundError:
996 print(f
"create symlink {dest}")
997 os.symlink(base, dest)
1002 Main function for the command line interface.
1004 it will automatically create an ArgumentParser including all functions which
1005 start with command_ in the global namespace as sub commands. These
1006 functions should take the arguments as first argument and an instance of the
1007 ConditionsDB interface as second argument. If the db interface is None the
1008 first argument is an instance of argparse.ArgumentParser an in this case the
1009 function should just add all needed arguments to the argument parser and
1014 logging.enable_summary(
False)
1016 logging.enable_python_logging =
True
1018 for level
in LogLevel.values.values():
1019 logging.set_info(level, LogInfo.LEVEL | LogInfo.MESSAGE)
1023 sys.argv[0:1] = os.path.basename(sys.argv[0]).split(
'-')
1026 parser = get_argument_parser()
1030 args = parser.parse_args()
1037 nprocess = getattr(args,
"nprocess", 1)
1038 retries = getattr(args,
"retries", 0)
1041 B2WARNING(
"-j must be larger than zero, ignoring")
1042 args.nprocess = nprocess = 1
1044 conditions_db =
ConditionsDB(args.base_url, nprocess, retries)
1046 if args.http_auth !=
"none":
1047 conditions_db.set_authentication(args.http_user, args.http_password, args.http_auth ==
"basic")
1050 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.