Source code for nolearn.console
"""The console module contains the :class:`Command` class that's
useful for building command-line scripts.
Consider a function `myfunc` that you want to call directly from the
command-line, but you want to avoid writing glue that deals with
argument parsing, converting those arguments to Python types and
passing them to other functions. Here's how `myfunc` could look like:
.. code-block:: python
def myfunc(a_string, a_list):
print a_string in a_list
`myfunc` takes two arguments, one is expeced to be a string, the other
one a list.
Let's use :class:`Command` to build a console script:
.. code-block:: python
from nolearn.console import Command
__doc__ = '''
Usage:
myprogram myfunc <config_file> [options]
'''
schema = '''
[myfunc]
a_string = string
a_list = listofstrings
'''
class Main(Command):
__doc__ = __doc__
schema = schema
funcs = [myfunc]
main = Main()
Note how we define a `schema` that has a definition of `myfunc`'s
arguments and their types. See :mod:`nolearn.inischema` for more
details on that.
We can then include this `main` function in our `setup.py` to get a
console script:
.. code-block:: python
setup(
name='myprogram',
# ...
entry_points='''
[console_scripts]
myprogram = myprogram.mymodule.main
''',
)
With this in place, you can now call the `myprogram` script like so:
.. code-block:: bash
$ myprogram myfunc args.ini
Where `args.ini` might look like:
.. code-block:: ini
[myfunc]
a_string = needle
a_list = haystack haystack needle haystack haystack
These constitute the two named arguments that will be passed into
`myfunc`. Passing of values is always done through `.ini` files.
You may also call your script with a `--profile=<fn>` option, which
you can use to profile your program using Python's standard
:mod:`cProfile` module.
A `--pdb` option is also available which allows you to automatically
enter post-mortem debugging when your script exits abnormally.
"""
import cProfile
import pdb
import os
import sys
import traceback
import docopt
from .inischema import parse_config
DEFAULT_OPTIONS = """
Options:
-h --help Show this screen
--pdb Do post mortem debugging on errors
--profile=<fn> Save a profile to <fn>
"""
[docs]class Command(object):
__doc__ = None
schema = None
funcs = []
def __init__(self, **kwargs):
vars(self).update(kwargs)
def doc(self):
doc = self.__doc__
if 'Options:' not in doc:
doc = doc + DEFAULT_OPTIONS
return doc
def __call__(self, argv=sys.argv):
doc = self.doc()
arguments = docopt.docopt(doc, argv=argv[1:])
self.arguments = arguments
for func in self.funcs:
if arguments[func.__name__]:
break
else: # pragma: no cover
raise KeyError("No function found to call.")
with open(arguments['<config_file>']) as config_file:
self.config = parse_config(self.schema, config_file.read())
env = self.config.get('env', {})
for key, value in env.items():
os.environ[key.upper()] = value
kwargs = self.config.get(func.__name__, {})
# If profiling, wrap the function with another one that does the
# profiling:
if arguments.get('--profile'):
func_ = func
def prof(**kwargs):
cProfile.runctx(
'func(**kwargs)',
globals(),
{'func': func_, 'kwargs': kwargs},
filename=arguments['--profile'],
)
func = prof
# If debugging, call pdb.post_mortem() in the except clause:
try:
func(**kwargs)
except:
if arguments.get('--pdb'):
traceback.print_exc()
pdb.post_mortem(sys.exc_traceback)
else: # pragma: no cover
raise