"""
..
Copyright (c) 2018 LG Electronics Inc.
SPDX-License-Identifier: GPL-3.0-or-later
Convert python code for Auptimizer automatically
================================================
See :doc:`experiment` for how to convert a job to a **Auptimizer** Experiment.
Basic Usage
-----------
::
python convert.py origin.py experiment.json demo_func
Additional arguments
--------------------
.. program-output:: python -m aup.convert -h
Example
-------
See `Examples/demo`:
APIs
----
"""
import json
import logging
import os
import stat
import click
[docs]def get_param(experiment_file):
"""Parse experiment file to retrieve hyperparameter names
:param experiment_file: JSON file of the experiment
:return: list of variable names
:rtype: [String]
"""
with open(experiment_file) as f:
j = json.load(f)
try:
return [i["name"] for i in j["parameter_config"]]
except KeyError as e:
if "parameter_config" not in j:
logging.fatal("parameter_config not in the experiment config")
else:
logging.fatal("name not in parameter_config")
raise e
[docs]def get_output_name(experiment_file):
"""Retrieves the Python script to be executed from the experiment json file"""
with open(experiment_file) as f:
j = json.load(f)
try:
return j["script"]
except KeyError as e:
logging.fatal("script need to be defined in experiment json")
raise e
[docs]def add_shenbang(script):
"""
Makes the Python script executable.
"""
if script.splitlines()[0][:2] != "#!":
#
if os.name == "posix":
return "#!/usr/bin/env python\n" + script
else:
logging.critical('Be cautious, add #!"C:\\Python33\\python.exe", make sure it executable on Windows')
return '#!"C:\\Python33\\python.exe\n' + script
else:
return script
[docs]def add_main(script):
"""
Adds a main function to the executable Python file.
"""
if "__main__" in script:
logging.critical("__main__ is already defined in the script. Make sure no duplicated __main__ blocks in output.")
return script + """\nif __name__ == "__main__":
import sys
from aup import BasicConfig, print_result
if len(sys.argv) != 2:
print("config file required")
exit(1)
config = BasicConfig().load(sys.argv[1])
aup_wrapper(config)\n"""
[docs]def add_func(script, func_name, variables):
"""
Adds wrapper function to the python script.
"""
arguments = ",".join(["{0}=config['{0}']".format(i) for i in variables])
wrapper_script = """\ndef aup_wrapper(config):
res = {0}({1})
print_result(res)\n""".format(func_name, arguments)
return script + wrapper_script
# TODO-handle exception: func_name does not have any return value (need some ast parsing)
# TODO-"config" is not used/referenced in "aup_wrapper" function
@click.command(name="auto convert script for Auptimizer",
context_settings=dict(help_option_names=['-h', '--help']))
@click.argument("script", type=click.Path(exists=True))
@click.argument("exp_json", type=click.Path(exists=True))
@click.argument("func_name", type=click.STRING)
@click.option("-o", "--output", type=click.STRING, default=None,
help="output file name")
def main(script, exp_json, func_name, output):
"""Convert script for Auptimizer
\b\n
Copyright (C) 2018 LG Electronics Inc.
\b\n
GPL-3.0 License. This program comes with ABSOLUTELY NO WARRANTY;
\b\n
Arguments:
script {str} -- Script name to train an ML model and return result
exp_json {str} -- JSON file name contrains experiment configuration (e.g. hyperparameter)
func_name {str} -- Name of the main function in the script for the training
\b\n
Raises:
Exception: If the script is not self-executable.
"""
variable_names = get_param(exp_json)
script = open(script).read()
script = add_shenbang(script)
script = add_func(script, func_name, variable_names)
script = add_main(script)
if output is None:
output = get_output_name(exp_json)
with open(output, 'w') as f:
f.write(script)
if os.name == "posix":
os.chmod(output, stat.S_IRWXU)
else:
logging.critical("Non-*nix OS is not fully supported, change permission by yourself.")
if not os.access(output, os.X_OK):
raise Exception("Failed at the last step - script %s is not executable" % output)
if __name__ == "__main__":
main()