5 This script provides a command line interface to all the tasks related to the
6 :ref:`Conditions database <conditionsdb_overview>`: manage globaltags and iovs as well as upload new payloads
7 or download of existing payloads.
9 The usage of this program is similar to git: there are sub commands like for
10 example ``tag`` wich groups all actions related to the management of global
11 tags. All the available commands are listed below.
32 from urllib.parse
import urljoin
36 from basf2
import B2ERROR, B2WARNING, B2INFO, LogLevel, LogInfo, logging, \
39 from terminal_utils
import Pager
40 from dateutil.parser
import parse
as parse_date
41 from getpass
import getuser
42 from .
import ConditionsDB, enable_debugging, encode_name, PayloadInformation
43 from .cli_utils
import ItemFilter
46 from .cli_upload
import command_upload
47 from .cli_download
import command_download, command_legacydownload
48 from .cli_management
import command_tag_merge, command_tag_runningupdate
51 def escape_ctrl_chars(name):
52 """Remove ANSI control characters from strings"""
54 if not hasattr(escape_ctrl_chars,
"_regex"):
55 escape_ctrl_chars._regex = re.compile(
"[\x00-\x1f\x7f-\x9f]")
59 if match.group(0).isspace():
61 return "\\x{:02x}".format(ord(match.group(0)))
63 return escape_ctrl_chars._regex.sub(escape, name)
66 def command_tag(args, db=None):
68 List, show, create, modify or clone globaltags.
70 This command allows to list, show, create modify or clone globaltags in the
71 central database. If no other command is given it will list all tags as if
72 "%(prog)s show" was given.
78 command_tag_list(args, db)
81 def command_tag_list(args, db=None):
83 List all available globaltags.
85 This command allows to list all globaltags, optionally limiting the output
86 to ones matching a given search term. By default invalidated globaltags
87 will not be included in the list, to show them as well please add
88 --with-invalid as option. Alternatively one can use --only-published to show
89 only tags which have been published
91 If the --regex option is supplied the searchterm will interpreted as a
92 python regular expression where the case is ignored.
95 tagfilter = ItemFilter(args)
98 args.add_argument(
"--detail", action=
"store_true", default=
False,
99 help=
"show detailed information for all tags instead "
101 group = args.add_mutually_exclusive_group()
102 group.add_argument(
"--with-invalid", action=
"store_true", default=
False,
103 help=
"if given also invalidated tags will be shown")
104 group.add_argument(
"--only-published", action=
"store_true", default=
False,
105 help=
"if given only published tags will be shown")
106 tagfilter.add_arguments(
"tags")
110 if not tagfilter.check_arguments():
113 req = db.request(
"GET",
"/globalTags",
"Getting list of globaltags{}".format(tagfilter))
117 for item
in req.json():
118 if not tagfilter.check(item[
"name"]):
120 tagstatus = item[
"globalTagStatus"][
"name"]
121 if not getattr(args,
"with_invalid",
False)
and tagstatus ==
"INVALID":
123 if getattr(args,
"only_published",
False)
and tagstatus !=
"PUBLISHED":
128 taglist.sort(key=
lambda x: x[
"name"])
132 with Pager(
"List of globaltags{}{}".format(tagfilter,
" (detailed)" if getattr(args,
"detail",
False)
else ""),
True):
134 if getattr(args,
"detail",
False):
135 print_globaltag(db, item)
140 escape_ctrl_chars(item.get(
"description",
"")),
141 item[
"globalTagType"][
"name"],
142 item[
"globalTagStatus"][
"name"],
143 item[
"payloadCount"],
146 if not getattr(args,
"detail",
False):
147 table.insert(0, [
"id",
"name",
"description",
"type",
"status",
"# payloads"])
148 pretty_print_table(table, [-10, 0,
"*", -10, -10, -10], min_flexible_width=32)
151 def print_globaltag(db, *tags):
152 """ Print detailed globaltag information for the given globaltags side by side"""
153 results = [[
"id"], [
"name"], [
"description"], [
"type"], [
"status"],
154 [
"# payloads"], [
"created"], [
"modified"], [
"modified by"]]
159 if isinstance(info, str):
161 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(info)),
162 "Getting info for globaltag {}".format(info))
163 except ConditionsDB.RequestError
as e:
171 created = parse_date(info[
"dtmIns"])
172 modified = parse_date(info[
"dtmMod"])
173 results[0].append(str(info[
"globalTagId"])),
174 results[1].append(info[
"name"]),
175 results[2].append(escape_ctrl_chars(info.get(
"description",
"")))
176 results[3].append(info[
"globalTagType"][
"name"])
177 results[4].append(info[
"globalTagStatus"][
"name"]),
178 results[5].append(info[
"payloadCount"]),
179 results[6].append(created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time"))
180 results[7].append(modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time"))
181 results[8].append(escape_ctrl_chars(info[
"modifiedBy"]))
183 ntags = len(results[0]) - 1
185 pretty_print_table(results, [11] + [
'*']*ntags,
True)
189 def change_state(db, tag, state, force=False):
190 """Change the state of a global tag
192 If the new state is not revertable then ask for confirmation
194 state = state.upper()
195 if state
in [
"INVALID",
"PUBLISHED"]
and not force:
196 name = input(f
"ATTENTION: Marking a tag as {state} cannot be undone.\n"
197 "If you are sure you want to continue it please enter the tag name again: ")
199 B2ERROR(
"Names don't match, aborting")
202 db.request(
"PUT", f
"/globalTag/{encode_name(tag)}/updateGlobalTagStatus/{state}",
203 f
"Changing globaltag state {tag} to {state}")
206 def command_tag_show(args, db=None):
208 Show details about globaltags
210 This command will show details for the given globaltags like name,
211 description and number of payloads.
219 args.add_argument(
"tag", metavar=
"TAGNAME", nargs=
"+", help=
"globaltags to show")
224 with Pager(
"globaltag Information",
True):
226 ntags += print_globaltag(db, tag)
229 return len(args.tag) - ntags
232 def command_tag_create(args, db=None):
234 Create a new globaltag
236 This command creates a new globaltag in the database with the given name
237 and description. The name can only contain alpha-numeric characters and the
242 args.add_argument(
"type", metavar=
"TYPE", help=
"type of the globaltag to create, usually one of DEV or RELEASE")
243 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"name of the globaltag to create")
244 args.add_argument(
"description", metavar=
"DESCRIPTION", help=
"description of the globaltag")
245 args.add_argument(
"-u",
"--user", metavar=
"USER", help=
"username who created the tag. "
246 "If not given we will try to supply a useful default")
250 info = {
"name": args.tag,
"description": args.description,
"modifiedBy": args.user,
"isDefault":
False}
252 if args.user
is None:
253 info[
"modifiedBy"] = os.environ.get(
"BELLE2_USER", getuser())
255 typeinfo = db.get_globalTagType(args.type)
259 req = db.request(
"POST",
"/globalTag/{}".format(encode_name(typeinfo[
"name"])),
260 "Creating globaltag {name}".format(**info),
262 B2INFO(
"Succesfully created globaltag {name} (id={globalTagId})".format(**req.json()))
265 def command_tag_modify(args, db=None):
267 Modify a globaltag by changing name or description
269 This command allows to change the name or description of an existing globaltag.
270 You can supply any combination of -n,-d,-t and only the given values will be changed
273 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to modify")
274 args.add_argument(
"-n",
"--name", help=
"new name")
275 args.add_argument(
"-d",
"--description", help=
"new description")
276 args.add_argument(
"-t",
"--type", help=
"new type of the globaltag")
277 args.add_argument(
"-u",
"--user", metavar=
"USER", help=
"username who created the tag. "
278 "If not given we will try to supply a useful default")
279 args.add_argument(
"-s",
"--state", help=
"new globaltag state, see the command ``tag state`` for details")
283 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(args.tag)),
284 "Getting info for globaltag {}".format(args.tag))
288 old_name = info[
"name"]
290 for key
in [
"name",
"description"]:
291 value = getattr(args, key)
292 if value
is not None and value != info[key]:
296 info[
"modifiedBy"] = os.environ.get(
"BELLE2_USER", os.getlogin())
if args.user
is None else args.user
298 if args.type
is not None:
300 typeinfo = db.get_globalTagType(args.type)
304 if info[
'gloalTagType'] != typeinfo:
305 info[
"globalTagType"] = typeinfo
310 db.request(
"PUT",
"/globalTag",
311 "Modifying globaltag {} (id={globalTagId})".format(old_name, **info),
314 if args.state
is not None:
315 name = args.name
if args.name
is not None else old_name
316 return change_state(db, name, args.state)
319 def command_tag_clone(args, db=None):
321 Clone a given globaltag including all IoVs
323 This command allows to clone a given globaltag with a new name but still
324 containing all the IoVs defined in the original globaltag.
328 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to be cloned")
329 args.add_argument(
"name", metavar=
"NEWNAME", help=
"name of the cloned globaltag")
333 req = db.request(
"GET",
"/globalTag/{}".format(encode_name(args.tag)),
334 "Getting info for globaltag {}".format(args.tag))
338 req = db.request(
"POST",
"/globalTags/{globalTagId}".format(**info),
339 "Cloning globaltag {name} (id={globalTagId})".format(**info))
343 cloned_info = req.json()
344 cloned_info[
"name"] = args.name
346 db.request(
"PUT",
"/globalTag",
"Renaming globaltag {name} (id={globalTagId})".format(**cloned_info),
350 def command_tag_state(args, db):
352 Change the state of a globaltag.
354 This command changes the state of a globaltag to the given value.
356 Usually the valid states are
359 Tag can be modified, payloads and iovs can be created and deleted. This
360 is the default state for new or cloned globaltags and is not suitable
361 for use in data analysis
363 Can be transitioned to TESTING, RUNNING
366 Tag cannot be modified and is suitable for testing but can be reopened
368 Can be transitioned to VALIDATED, OPEN
371 Tag cannot be modified and has been tested.
373 Can be transitioned to PUBLISHED, OPEN
376 Tag cannot be modified and is suitable for user analysis
378 Can only be transitioned to INVALID
381 Tag can only be modified by adding new runs, not modifying the payloads
385 Tag is invalid and should not be used for anything.
387 This state is end of life for a globaltag and cannot be transitioned to
390 .. versionadded:: release-04-00-00
393 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to be changed")
394 args.add_argument(
"state", metavar=
"STATE", help=
"new state for the globaltag")
395 args.add_argument(
"--force", default=
False, action=
"store_true", help=argparse.SUPPRESS)
398 return change_state(db, args.tag, args.state, args.force)
401 def command_tag_publish(args, db):
405 This command sets the state of a globaltag to PUBLISHED. This will make the
406 tag immutable and no more modifications are possible. A confirmation dialog
409 .. deprecated:: release-04-00-00
410 Use ``tag state $name PUBLISHED`` instead
413 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to be published")
416 return change_state(db, args.tag,
"PUBLISHED")
419 def command_tag_invalidate(args, db):
421 Invalidate a globaltag.
423 This command ets the state of a globaltag to INVALID. This will disqualify
424 this tag from being used in user analysis. A confirmation dialog will be
427 .. deprecated:: release-04-00-00
428 Use ``tag state $name PUBLISHED`` instead
431 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag to be invalidated")
434 return change_state(db, args.tag,
"INVALID")
437 def remove_repeated_values(table, columns, keep=None):
438 """Strip repeated values from a table of values
440 This function takes a table (a list of lists with all the same length) and
441 removes values in certain columns if they are identical in consecutive rows.
443 It does this in a dependent way only if the previous columns are identical
444 will it continue stripping further columns. For example, given the table ::
453 If we want to remove duplicates in all columns in order it would look like this:
455 >>> remove_repeated_values(table, [0,1])
463 But we can give selected columns to strip one after the other
465 >>> remove_repeated_values(table, [1,0])
473 In addition, we might want to only strip some columns if previous columns
474 were identical but keep the values of the previous column. For this one can
477 >>> remove_repeated_values(table, [0,1,2], keep=[0])
486 table (list(list(str))): 2d table of values
487 columns (list(int)): indices of columns to consider in order
488 keep (set(int)): indices of columns to not strip
490 last_values = [
None] * len(columns)
491 for row
in table[1:]:
492 current_values = [row[i]
for i
in columns]
493 for i, curr, last
in zip(columns, current_values, last_values):
497 if keep
and i
in keep:
502 last_values = current_values
505 def command_diff(args, db):
506 """Compare two globaltags
508 This command allows to compare two globaltags. It will show the changes in
509 a format similar to a unified diff but by default it will not show any
510 context, only the new or removed payloads. Added payloads are marked with a
511 ``+`` in the first column, removed payloads with a ``-``
513 If ``--full`` is given it will show all payloads, even the ones common to
514 both globaltags. The differences can be limited to a given run and
515 limited to a set of payloads names using ``--filter`` or ``--exclude``. If
516 the ``--regex`` option is supplied the searchterm will interpreted as a
517 python regular expression where the case is ignored.
519 .. versionchanged:: release-03-00-00
520 modified output structure and added ``--human-readable``
521 .. versionchanged:: after release-04-00-00
522 added parameter ``--checksums`` and ``--show-ids``
524 iovfilter = ItemFilter(args)
526 args.add_argument(
"--full", default=
False, action=
"store_true",
527 help=
"If given print all iovs, also those which are the same in both tags")
528 args.add_argument(
"--run", type=int, nargs=2, metavar=
"N", help=
"exp and run numbers "
529 "to limit showing iovs to a ones present in a given run")
530 args.add_argument(
"--human-readable", default=
False, action=
"store_true",
531 help=
"If given the iovs will be written in a more human friendly format. "
532 "Also repeated payload names will be omitted to create a more readable listing.")
533 args.add_argument(
"--checksums", default=
False, action=
"store_true",
534 help=
"If given don't show the revision number but the md5 checksum")
535 args.add_argument(
"--show-ids", default=
False, action=
"store_true",
536 help=
"If given also show the payload and iov ids for each iov")
538 args.add_argument(
"tagA", metavar=
"TAGNAME1", help=
"base for comparison")
539 args.add_argument(
"tagB", metavar=
"TAGNAME2", help=
"tagname to compare")
540 iovfilter.add_arguments(
"payloads")
544 if not iovfilter.check_arguments():
547 with Pager(f
"Differences between globaltags {args.tagA} and {args.tagB}{iovfilter}",
True):
548 print(
"globaltags to be compared:")
549 ntags = print_globaltag(db, args.tagA, args.tagB)
553 listA = [e
for e
in db.get_all_iovs(args.tagA, message=str(iovfilter))
if iovfilter.check(e.name)]
554 listB = [e
for e
in db.get_all_iovs(args.tagB, message=str(iovfilter))
if iovfilter.check(e.name)]
556 B2INFO(
"Comparing contents ...")
557 diff = difflib.SequenceMatcher(a=listA, b=listB)
558 table = [[
"",
"Name",
"Rev" if not args.checksums
else "Checksum"]]
559 columns = [1,
"+", -8
if not args.checksums
else -32]
561 if args.human_readable:
565 table[0] += [
"First Exp",
"First Run",
"Final Exp",
"Final Run"]
566 columns += [6, 6, 6, 6]
569 table[0] += [
"IovId",
"PayloadId"]
572 def add_payloads(opcode, payloads):
573 """Add a list of payloads to the table, filling the first column with opcode"""
575 row = [opcode, p.name, p.revision
if not args.checksums
else p.checksum]
576 if args.human_readable:
577 row += [p.readable_iov()]
582 row += [p.iov_id, p.payload_id]
585 for tag, i1, i2, j1, j2
in diff.get_opcodes():
589 add_payloads(
" ", listB[j1:j2])
590 if tag
in [
"delete",
"replace"]:
591 add_payloads(
"-", listA[i1:i2])
592 if tag
in [
"insert",
"replace"]:
593 add_payloads(
"+", listB[j1:j2])
595 if args.human_readable:
599 remove_repeated_values(table, [0, 1, 2] + ([-1]
if args.show_ids
else []), keep=[0])
601 def color_row(row, widths, line):
602 if not LogPythonInterface.terminal_supports_colors():
604 begin = {
'+':
'\x1b[32m',
'-':
'\x1b[31m'}.get(row[0],
"")
606 return begin + line + end
611 print(f
" Differences between {args.tagA} and {args.tagB}")
612 pretty_print_table(table, columns, transform=color_row,
613 hline_formatter=
lambda w:
" " + (w-1)*
'-')
616 def command_iov(args, db):
618 List all IoVs defined in a globaltag, optionally limited to a run range
620 This command lists all IoVs defined in a given globaltag. The list can be
621 limited to a given run and optionally searched using --filter or --exclude.
622 If the --regex option is supplied the searchterm will interpreted as a
623 python regular expression where the case is ignored.
625 .. versionchanged:: release-03-00-00
626 modified output structure and added ``--human-readable``
627 .. versionchanged:: after release-04-00-00
628 added parameter ``--checksums`` and ``--show-ids``
631 iovfilter = ItemFilter(args)
634 args.add_argument(
"tag", metavar=
"TAGNAME", help=
"globaltag for which the the IoVs should be listed")
635 args.add_argument(
"--run", type=int, nargs=2, metavar=
"N", help=
"exp and run numbers "
636 "to limit showing iovs to a ones present in a given run")
637 args.add_argument(
"--detail", action=
"store_true", default=
False,
638 help=
"if given show a detailed information for all "
639 "IoVs including details of the payloads")
640 args.add_argument(
"--human-readable", default=
False, action=
"store_true",
641 help=
"If given the iovs will be written in a more human friendly format. "
642 "Also repeated payload names will be omitted to create a more readable listing.")
643 args.add_argument(
"--checksums", default=
False, action=
"store_true",
644 help=
"If given don't show the revision number but the md5 checksum")
645 args.add_argument(
"--show-ids", default=
False, action=
"store_true",
646 help=
"If given also show the payload and iov ids for each iov")
647 iovfilter.add_arguments(
"payloads")
651 if not iovfilter.check_arguments():
654 if args.run
is not None:
655 msg =
"Obtaining list of iovs for globaltag {tag}, exp={exp}, run={run}{filter}".format(
656 tag=args.tag, exp=args.run[0], run=args.run[1], filter=iovfilter)
657 req = db.request(
"GET",
"/iovPayloads", msg, params={
'gtName': args.tag,
'expNumber': args.run[0],
658 'runNumber': args.run[1]})
660 msg =
"Obtaining list of iovs for globaltag {tag}{filter}".format(tag=args.tag, filter=iovfilter)
661 req = db.request(
"GET",
"/globalTag/{}/globalTagPayloads".format(encode_name(args.tag)), msg)
663 with Pager(
"List of IoVs{}{}".format(iovfilter,
" (detailed)" if args.detail
else ""),
True):
665 for item
in req.json():
666 payload = item[
"payload" if 'payload' in item
else "payloadId"]
667 if "payloadIov" in item:
668 iovs = [item[
'payloadIov']]
670 iovs = item[
'payloadIovs']
672 if not iovfilter.check(payload[
'basf2Module'][
'name']):
679 iov_created = parse_date(iov[
"dtmIns"])
680 iov_modified = parse_date(iov[
"dtmMod"])
681 payload_created = parse_date(payload[
"dtmIns"])
682 payload_modified = parse_date(payload[
"dtmMod"])
684 [
"IoV Id", str(iov[
"payloadIovId"])],
685 [
"first experiment", iov[
"expStart"]],
686 [
"first run", iov[
"runStart"]],
687 [
"final experiment", iov[
"expEnd"]],
688 [
"final run", iov[
"runEnd"]],
689 [
"IoV created", iov_created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
690 [
"IoV modified", iov_modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
691 [
"IoV modified by", iov[
"modifiedBy"]],
692 [
"payload Id", str(payload[
"payloadId"])],
693 [
"name", payload[
"basf2Module"][
"name"]],
694 [
"revision", payload[
"revision"]],
695 [
"checksum", payload[
"checksum"]],
696 [
"payloadUrl", payload[
"payloadUrl"]],
697 [
"baseUrl", payload.get(
"baseUrl",
"")],
699 [
"payload created", payload_created.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
700 [
"payload modified", payload_modified.astimezone(tz=
None).strftime(
"%Y-%m-%d %H:%M:%S local time")],
701 [
"payload modified by", escape_ctrl_chars(payload[
"modifiedBy"])],
704 pretty_print_table(result, [-40,
'*'],
True)
706 payloads.append(PayloadInformation.from_json(payload, iov))
709 def add_ids(table, columns, payloads):
710 """Add the numerical ids to the table"""
712 table[0] += [
"IovId",
"PayloadId"]
714 for row, p
in zip(table[1:], payloads):
715 row += [p.iov_id, p.payload_id]
717 if args.human_readable:
718 table = [[
"Name",
"Rev" if not args.checksums
else "Checksum",
"IoV"]]
719 columns = [
"+", -8
if not args.checksums
else -32, -32]
720 table += [[p.name, p.revision
if not args.checksums
else p.checksum, p.readable_iov()]
for p
in payloads]
721 add_ids(table, columns, payloads)
723 remove_repeated_values(table, columns=[0, 1] + ([-1]
if args.show_ids
else []))
726 table = [[
"Name",
"Rev" if not args.checksums
else "Checksum",
"First Exp",
"First Run",
"Final Exp",
"Final Run"]]
727 table += [[p.name, p.revision
if not args.checksums
else p.checksum] + list(p.iov)
for p
in payloads]
728 columns = [
"+", -8
if not args.checksums
else -32, 6, 6, 6, 6]
729 add_ids(table, columns, payloads)
731 pretty_print_table(table, columns)
734 def command_dump(args, db):
736 Dump the content of a given payload
738 .. versionadded:: release-03-00-00
740 This command will dump the payload contents stored in a given payload. One
741 can either specify the payloadId (from a previous output of
742 ``b2conditionsdb iov``), the payload name and its revision in the central
743 database, or directly specify a local database payload file.
747 Dump the content of a previously downloaded payload file::
749 $ b2conditionsdb dump -f localdb/dbstore_BeamParameters_rev_59449.root
751 Dump the content of a payload by name and revision directly from the central database::
753 $ b2conditionsdb dump -r BeamParameters 59449
755 Dump the content of the payload by name which is valid in a given globaltag
756 for a given experiment and run::
758 $ b2conditionsdb dump -g BeamParameters master_2019-09-26 0 0
760 Or directly by payload id from a previous call to ``b2conditionsdb iov``::
762 $ b2conditionsdb dump -i 59685
766 Depending on whether you want to display a payload by its id in the
767 database, its name and revision in the database or from a local file
768 provide **one** of the arguments ``-i``, ``-r``, ``-f`` or ``-g``
770 .. versionchanged:: after release-04-00-00
771 added argument ``-r`` to directly dump a payload valid for a given run
775 group = args.add_mutually_exclusive_group(required=
True)
776 choice = group.add_mutually_exclusive_group()
777 choice.add_argument(
"-i",
"--id", metavar=
"PAYLOADID", help=
"payload id to dump")
778 choice.add_argument(
"-r",
"--revision", metavar=(
"NAME",
"REVISION"), nargs=2,
779 help=
"Name and revision of the payload to dump")
780 choice.add_argument(
"-f",
"--file", metavar=
"FILENAME", help=
"Dump local payload file")
781 choice.add_argument(
"-g",
"--valid", metavar=(
"NAME",
"GLOBALTAG",
"EXP",
"RUN"), nargs=4,
782 help=
"Dump the payload valid for the given exp, run number in the given globaltag")
783 args.add_argument(
"--show-typenames", default=
False, action=
"store_true",
784 help=
"If given show the type names of all classes. "
785 "This makes output more crowded but can be helpful for complex objects.")
786 args.add_argument(
"--show-streamerinfo", default=
False, action=
"store_true",
787 help=
"If given show the StreamerInfo for the classes in the the payload file. "
788 "This can be helpful to find out which version of a payload object "
789 "is included and what are the members")
797 if not os.path.isfile(filename):
798 B2ERROR(f
"Payloadfile {filename} could not be found")
801 match = re.match(
r"^dbstore_(.*)_rev_(.*).root$", os.path.basename(filename))
803 match = re.match(
r"^(.*)_r(.*).root$", os.path.basename(filename))
805 B2ERROR(
"Filename doesn't follow database convention.\n"
806 "Should be 'dbstore_${payloadname}_rev_${revision}.root' or '${payloadname}_r${revision.root}'")
808 name = match.group(1)
809 revision = match.group(2)
810 payloadId =
"Unknown"
815 req = db.request(
"GET", f
"/payload/{args.id}",
"Getting payload info")
816 payload = PayloadInformation.from_json(req.json())
819 name, rev = args.revision
821 req = db.request(
"GET", f
"/module/{encode_name(name)}/payloads",
"Getting payload info")
823 if p[
"revision"] == rev:
824 payload = PayloadInformation.from_json(p)
827 B2ERROR(f
"Cannot find payload {name} with revision {rev}")
830 name, globaltag, exp, run = args.valid
832 for p
in db.get_all_iovs(globaltag, exp, run, f
", name={name}"):
833 if p.name == name
and (payload
is None or p.revision > payload.revision):
837 B2ERROR(f
"Cannot find payload {name} in globaltag {globaltag} for exp,run {exp},{run}")
840 filename = payload.url
841 revision = payload.revision
842 payloadId = payload.payload_id
846 from ROOT
import TFile, TBufferJSON, cout
849 tfile = TFile.Open(filename)
852 if not tfile
or not tfile.IsOpen():
854 contents = db._session.get(filename, stream=
True)
855 if contents.status_code != requests.codes.ok:
856 B2ERROR(f
"Could not open payload file {filename}")
858 raw_contents = contents.raw.read().decode()
860 obj = tfile.Get(name)
862 json_str = TBufferJSON.ConvertToJSON(obj)
866 Drop some members from ROOT json output.
868 We do not care about fBits, fUniqueID or the typename of sub classes,
869 we assume users are only interested in the data stored in the member
872 obj.pop(
"fBits",
None)
873 obj.pop(
"fUniqueID",
None)
874 if not args.show_typenames:
875 obj.pop(
"_typename",
None)
878 with Pager(f
"Contents of Payload {name}, revision {revision} (id {payloadId})",
True):
879 if args.show_streamerinfo
and tfile:
880 B2INFO(
"StreamerInfo of Payload {name}, revision {revision} (id {payloadId})")
881 tfile.ShowStreamerInfo()
888 if json_str
is not None:
889 B2INFO(f
"Contents of Payload {name}, revision {revision} (id {payloadId})")
892 obj = json.loads(json_str.Data(), object_hook=drop_fbits)
894 pprint.pprint(obj, compact=
True, width=shutil.get_terminal_size((80, 20))[0])
896 B2INFO(f
"Raw contents of Payload {name}, revision {revision} (id {payloadId})")
897 print(escape_ctrl_chars(raw_contents))
899 B2INFO(f
"ROOT contents of Payload {name}, revision {revision} (id {payloadId})")
900 B2WARNING(
"The payload is a valid ROOT file but doesn't contain a payload object with the expected name. "
901 " Automated display of file contents are not possible, showing just entries in the ROOT file.")
906 """Class to recusively show help for an ArgumentParser and all it's sub_parsers"""
909 """Print help message for given parser and call again for all sub parsers"""
911 subparsers_actions = [
912 action
for action
in parser._actions
913 if isinstance(action, argparse._SubParsersAction)]
916 for subparsers_action
in subparsers_actions:
918 for choice, subparser
in subparsers_action.choices.items():
920 print(
"Command '{}{}'".format(prefix, choice))
921 print(subparser.format_help())
925 def __call__(self, parser, namespace, values, option_string=None):
926 """Show full help message"""
928 with Pager(
"{} {}".format(parser.prog, option_string)):
934 def get_argument_parser():
936 Build a parser with all arguments of all commands
939 options = argparse.ArgumentParser(add_help=
False)
940 options.add_argument(
"--debugging", action=
"store_true",
941 help=
"Enable debugging of http traffic")
942 options.add_argument(
"--help-full", action=FullHelpAction,
943 help=
"show help message for all commands and exit")
944 options.add_argument(
"--base-url", default=
None,
945 help=
"URI for the base of the REST API, if not given a list of default locations is tried")
946 options.add_argument(
"--http-auth", choices=[
"none",
"basic",
"digest"], default=
"basic",
947 help=argparse.SUPPRESS)
948 options.add_argument(
"--http-user", default=
"commonDBUser", help=argparse.SUPPRESS)
949 options.add_argument(
"--http-password", default=
"Eil9ohphoo2quot", help=argparse.SUPPRESS)
951 parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, parents=[options])
952 parser.set_defaults(func=
lambda x, y: parser.print_help())
953 parsers = parser.add_subparsers(
954 title=
"Top level commands",
955 description=
"To get additional help, run '%(prog)s COMMAND --help'"
960 for name, func
in sorted(globals().items()):
961 if not name.startswith(
"command_"):
966 parts = name.split(
'_')[1:]
972 parent_parser, parent = subparsers[tuple(parts[:-1])]
976 parent = parent_parser.add_subparsers(
977 title=
"sub commands",
978 description=
"To get additional help, run '%(prog)s COMMAND --help'"
980 subparsers[tuple(parts[:-1])][1] = parent
985 helptxt, description = textwrap.dedent(func.__doc__).split(
"\n\n", 1)
986 command_parser = parent.add_parser(parts[-1], help=helptxt, add_help=
True, description=description,
987 parents=[options], formatter_class=argparse.RawDescriptionHelpFormatter)
990 func(command_parser,
None)
992 command_parser.set_defaults(func=func)
994 subparsers[tuple(parts)] = [command_parser,
None]
999 def create_symlinks(base):
1000 """Create symlinks from base to all subcommands.
1002 e.g. if the base is ``b2conditionsdb`` then this command will create symlinks
1003 like ``b2conditionsdb-tag-show`` in the same directory
1005 When adding a new command to b2conditionsdb this function needs to be executed
1006 in the framework tools directory
1008 python3 -c 'from conditions_db import cli_main; cli_main.create_symlinks("b2conditionsdb")'
1014 for name
in sorted(globals().keys()):
1015 if not name.startswith(
"command_"):
1017 parts = name.split(
"_")[1:]
1018 if parts
in excluded:
1020 dest = base +
"-".join([
""] + parts)
1024 except FileNotFoundError:
1026 print(f
"create symlink {dest}")
1027 os.symlink(base, dest)
1032 Main function for the command line interface.
1034 it will automatically create an ArgumentParser including all functions which
1035 start with command_ in the global namespace as sub commmands. These
1036 functions should take the arguments as first argument and an instance of the
1037 ConditionsDB interface as second argument. If the db interface is None the
1038 first argument is an instance of argparse.ArgumentParser an in this case the
1039 function should just add all needed arguments to the argument parser and
1044 logging.enable_summary(
False)
1046 logging.enable_python_logging =
True
1048 for level
in LogLevel.values.values():
1049 logging.set_info(level, LogInfo.LEVEL | LogInfo.MESSAGE)
1053 sys.argv[0:1] = os.path.basename(sys.argv[0]).split(
'-')
1056 parser = get_argument_parser()
1060 args = parser.parse_args()
1067 nprocess = getattr(args,
"nprocess", 1)
1068 retries = getattr(args,
"retries", 0)
1071 B2WARNING(
"-j must be larger than zero, ignoring")
1072 args.nprocess = nprocess = 1
1074 conditions_db = ConditionsDB(args.base_url, nprocess, retries)
1076 if args.http_auth !=
"none":
1077 conditions_db.set_authentication(args.http_user, args.http_password, args.http_auth ==
"basic")
1080 return args.func(args, conditions_db)