Source code for mmf.utils.run
r"""Running Programs
This module is intended to be used to manage important runs that generate data
for a paper, etc.
For interactive use, start python as follows::
python -im mmf.utils.run
If you need to run a script, then use this module as the startup file with one
of the following commands::
PYTHONSTARTUP=path_to_mmf/mmf/utils/run.py python file.py
PYTHONSTARTUP=path_to_mmf/mmf/utils/run.py python -c "import blah;..."
To find the full path for the previous commands, run::
python -c"import sys;sys._pymmf_no_init=True;import mmf;print mmf.run_cmd()"
This can be aliased for easy use.
Purpose
-------
The aim of this module is reproducibility:
1) This module assumes that the application be run in an appropriately named
directory: no special sub-directory will be created (other than those
associated with :class:`mmf.archive.DataSet` instances).
2) The command must be run in a mercurial repository with no outstanding
commits. The current revision number and checksum is used for output. These
checks can be temporarily disabled by setting the environmental variable
`STRICT=false`, but this should be avoided except during testing as it breaks
the reproducibility.
3) All input and output is recorded.
4) ``pip`` is used to record the versions of libraries installed when the
program is run.
"""
import os.path
import subprocess
import sys
import logging
[docs]def run_cmd():
r"""Return the run command for running python with this module as the
startup file."""
return 'PYTHONSTARTUP="' + os.path.realpath(__file__) + '" python'
[docs]def get_hg_checksum(strict=True):
r"""Return a string of the form `<rev>_<checksum>` where `<rev>` is the
local revision number and `<checksum>` is the checksum of the current
revision. (Note: `<rev>` is not unique and may vary from repository to
repository, but provides a simple chronological ordering for runs that is
not intuitively deducible from the `<checksum>`.)
Parameters
----------
strict : bool
If `True`, then this will raise a :exc:`ValueError` exception if the
there are uncommited changes.
"""
try:
subprocess.check_output(["hg","--version"]).strip()
except:
print("ERROR: Could not execute Mercurial (hg)!")
raise
try:
hg_checksum = subprocess.check_output(["hg","-q","id"]).strip()
except:
print("ERROR: Could not get version! (Is there are repository here?)")
raise
try:
hg_local_revision = subprocess.check_output(["hg","id","-n"]).strip()
except:
print("ERROR: Could not get local revision!")
raise
if len(hg_checksum) not in [12, 13]:
raise ValueError(
'Please run from within the mercurial repo. "hg -1 id" gave:\n' +
hg_checksum)
if (strict and hg_checksum.endswith('+')):
raise ValueError(
'Repository has uncommited changes! ' +
'Commit ("hg com") before running.')
hg_checksum = "%s_%s" % (hg_local_revision, hg_checksum)
return hg_checksum
[docs]def get_pip_requirements():
r"""Return the ``pip`` requirements file as a string.
This is the output of ``pip freeze``.
"""
try:
requirements = subprocess.check_output(["pip","freeze"]).strip()
except:
print("ERROR: Could not run pip!")
raise
return requirements
if "__main__" == __name__:
import datetime
start_time = datetime.datetime.now()
import mmf.utils.logging
strict = (os.environ.get('STRICT', "True").lower() != "false")
hg_checksum = get_hg_checksum(strict)
pid = os.getpid()
prefix = "%s_%s_pid%i" % (
hg_checksum,
datetime.datetime.isoformat(start_time).replace(':','-'),
pid)
requirements_file = prefix + "_requirements.txt"
with open(requirements_file, 'w') as f:
f.write(get_pip_requirements())
mmf.utils.logging.start_logging(
log_warnings=True,
files=[
(prefix + ".log"),
(prefix + "_debug.log", dict(level=logging.DEBUG, stack=False)),
(prefix + "_trace.log", dict(level=logging.ERROR, stack=True)),
],
debug_level=logging.ERROR,
level=logging.INFO,
interactive='reset',
)