diff --git a/webservice/update_config.py b/webservice/update_config.py old mode 100644 new mode 100755 index c794742367810190be1e8514800af79d5464d227..59e4a186109115a1e3798d18d17686e997727f17 --- a/webservice/update_config.py +++ b/webservice/update_config.py @@ -18,7 +18,6 @@ agipd_options = { 'msg': "Range list of maximum pulse indices " "(--max-pulses start end step). " "3 max input elements. "}, - "calfile": {'typ': str}, } data_mapping = { @@ -32,7 +31,11 @@ available_options = { "MID_DET_AGIPD1M-1": [agipd_options, data_mapping] } -formatter = lambda prog: argparse.HelpFormatter(prog, max_help_position=52) + +def formatter(prog): + return argparse.HelpFormatter(prog, max_help_position=52) + + parser = argparse.ArgumentParser( description='Request update of configuration', formatter_class=formatter) required_args = parser.add_argument_group('required arguments') @@ -46,6 +49,13 @@ required_args.add_argument('--cycle', type=str, help='The facility cycle.') parser.add_argument('--apply', action='store_true', help='Apply and push the requested ' 'configuration update to the git.') +parser.add_argument( + '--webservice-address', + type=str, + default="tcp://max-exfl016:5555", + help=('The port of the webservice to update ' + 'calibration configurations repository.') +) # remove help calls as they will cause the argument parser to exit add_help = False if "-h" in sys.argv: @@ -58,10 +68,11 @@ if "--help" in sys.argv: known, remaining = parser.parse_known_args() args = vars(known) karabo_id = args["karabo_id"] -bool_keys = [] +webservice_address = args["webservice_address"] +bool_keys = [] -# Avoid erros when karabo_id not yet given through the command line. +# Avoid erros when karabo_id not given through the command line. if karabo_id is not None: # adding "no" bools to available options for det, val in available_options[karabo_id][0].items(): @@ -69,38 +80,46 @@ if karabo_id is not None: bool_keys.append(det) for b in bool_keys: - available_options[karabo_id][0]['no-{}'.format(det)] = {'typ': bool} + available_options[karabo_id][0][f'no-{b}'] = {'typ': bool} for exposed_options in available_options[karabo_id]: for option, info in exposed_options.items(): - metavar = None + typ = info['typ'] + choices = info.get('choices') if info['typ'] == list: - nargs = '+' + arguments = { + "action": 'append', + "nargs": '+', + } typ = str - action = 'append' # Avoid having a big line of choices in the help message. if 'choices' in info.keys(): - metavar = option.upper() + arguments.update({ + "metavar": option.upper(), + "choices": choices, + }) else: - action = None - nargs = None - typ = info['typ'] + arguments = { + "choices": choices, + } # Add help messages help_msg = "" if 'msg' in info.keys(): help_msg += info['msg'] - if 'choices' in info.keys(): - choices = info['choices'] - else: - choices = None - help_msg += f"Type: {info['typ'].__name__} ".upper() - parser.add_argument(f"--{option}", type=typ, action=action, - metavar=metavar, nargs=nargs, choices=choices, - help=help_msg) - -parser.add_argument('--instrument', type=str, choices=["CALLAB"], - help='This is only used for testing purposes.') + help_msg += f"Type: {info['typ'].__name__} ".upper() + parser.add_argument( + f"--{option}", + type=typ, + help=help_msg, + **arguments + ) + +parser.add_argument( + '--instrument', + type=str, choices=["CALLAB"], + help='This is only used for testing purposes.' +) if add_help: sys.argv.append("--help") @@ -135,10 +154,7 @@ for key, value in args.items(): new_conf[task][instrument][karabo_id][key.replace('no-', '')] = False # noqa # avoid saving the "no-"key into the updated config continue - # Assure adding an empty string for new empty - # str. updates (e.g. calfile) - if isinstance(key, str) and (value == '' or value == ' '): - value = '""' + # checking if data-mapping was updated. if key in data_mapping.keys(): if 'data-mapping' not in new_conf.keys(): @@ -160,7 +176,7 @@ print(f"Sending the following update: \n {pyaml}") print("-" * 80) con = zmq.Context() socket = con.socket(zmq.REQ) -con = socket.connect("tcp://max-exfl016:5555") +con = socket.connect(webservice_address) msg = "','".join(["update_conf", "SASEX", args["karabo_id"], instrument, args["cycle"], args["proposal"], json.dumps(new_conf), str(args["apply"])]) diff --git a/webservice/webservice.py b/webservice/webservice.py index d19226099853280c673a416c7ea7448a86113905..f7684dc86f4187daee343b06217d5af8d62b3d72 100644 --- a/webservice/webservice.py +++ b/webservice/webservice.py @@ -171,9 +171,8 @@ def merge(source: Dict, destination: Dict) -> Dict: def change_config(config, updated_config, karabo_id, instrument, - cycle, proposal, apply=False) -> bytes: - """ - Change the configuration of a proposal + cycle, proposal, apply=False) -> bytes: + """ Change the configuration of a proposal If no proposal specific configuration yet exists, one is first created based on the default configuration of the proposal @@ -182,44 +181,56 @@ def change_config(config, updated_config, karabo_id, instrument, :param config: repo config as given in YAML config file :param updated_config: a dictionary containing the updated config - :param instrument: the instrument to change config for + :param karabo_id: karabo detector identifier. + :param instrument: the instrument to change the config for :param cycle: the cycle to change config for :param proposal: the proposal to change config for :param apply: set to True to actually commit a change, otherwise a dry-run is performed :return: The updated config to the requesting zmq socket """ - # first check if a proposal specific config exists, if not create one repo = Repo(config['local-path']) repo.remote().pull() - prop_dir = os.path.join(repo.working_tree_dir, cycle) - os.makedirs(prop_dir, exist_ok=True) - fpath = "{}/{:06d}.yaml".format(prop_dir, int(proposal)) - if not os.path.exists(fpath): - with open("{}/default.yaml".format(repo.working_tree_dir), "r") as f: - defconf = yaml.load(f.read(), Loader=yaml.FullLoader) + # A cycle directory is created, if doesn't exists. + cyc_dir = Path(repo.working_tree_dir, cycle) + cyc_dir.mkdir(exist_ok=True) + fpath = Path(cyc_dir, f"{int(proposal):06d}.yaml") + + # In case cycle/proposal.yaml doesn't exist, + # new file is created based on default.yaml + if not fpath.exists(): + with open(f"{repo.working_tree_dir}/default.yaml", "r") as f: + defconf = yaml.safe_load(f) subconf = {} - for action, instruments in defconf.items(): + # Propsal.yaml should have dark, correct and data-mapping keys + # with all detector dictionaries of the changed instrument. + for action in ["dark", "correct"]: subconf[action] = {} - if action != "data-mapping": - subconf[action][instrument] = instruments[instrument] - else: - subconf[action][karabo_id] = instruments[karabo_id] + subconf[action][instrument] = defconf[action][instrument] + + # Copy data-mapping for all detectors of an instrument. + subconf["data-mapping"] = {} + for k_id in defconf["dark"][instrument].keys(): + subconf["data-mapping"][k_id] = defconf["data-mapping"][k_id] with open(fpath, "w") as wf: - wf.write(yaml.dump(subconf, default_flow_style=False)) + wf.write(yaml.safe_dump(subconf, default_flow_style=False)) + new_conf = None with open(fpath, "r") as rf: - existing_conf = yaml.load(rf.read(), Loader=yaml.FullLoader) + existing_conf = yaml.safe_load(rf) new_conf = merge(updated_config, existing_conf) + if apply: + # Apply updated configuration to the proposal.yaml + # and push it to the calibration_configurations remote reporsitory. with open(fpath, "w") as wf: - wf.write(yaml.dump(new_conf, default_flow_style=False)) - repo.index.add([fpath]) + wf.write(yaml.safe_dump(new_conf, default_flow_style=False)) + repo.index.add([str(fpath)]) repo.index.commit( - "Update to proposal YAML: {}".format(datetime.now().isoformat())) + f"Update to proposal YAML: {datetime.now().isoformat()}") repo.remote().push() logging.info(Success.UPLOADED_CONFIG.format(cycle, proposal)) - return yaml.dump(new_conf, default_flow_style=False).encode() + return yaml.safe_dump(new_conf, default_flow_style=False).encode() async def run_proc_async(cmd: List[str]) -> (int, bytes):