Skip to content

Commit

Permalink
Added more features to manage file
Browse files Browse the repository at this point in the history
  • Loading branch information
rochacbruno committed Jun 14, 2016
1 parent eec4560 commit 3b0e66c
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 43 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Edit the file :code:`manage.yml` with the following content::
- b
kwargs:
foo: bar
init_script: -
init_script: |
from foo import bar
bar.configure()

Expand Down
42 changes: 42 additions & 0 deletions manage/auto_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# coding: utf-8
from manage.utils import import_string


def get_name(obj, default):
default = default.split('.')[0]
return getattr(obj, '__name__', default)


def import_objects(manage_dict):
auto_import = {}
auto_scripts = []
import_dict = manage_dict.get('shell', {}).get('auto_import', {})
for name, spec in import_dict.get('objects', {}).items():
_obj = import_string(name)
if spec:
if 'init' in spec:
method_name = spec['init'].keys()[0]
args = spec['init'].get(method_name, {}).get('args', [])
kwargs = spec['init'].get(method_name, {}).get('kwargs', {})
getattr(_obj, method_name)(*args, **kwargs)
auto_import[spec.get('as', get_name(_obj, name))] = _obj
if 'init_script' in spec:
auto_scripts.append(spec['init_script'])
else:
auto_import[get_name(_obj, name)] = _obj
for script in auto_scripts:
exec(script, auto_import)
return auto_import


def exec_init(manage_dict, context):
for name, spec in manage_dict['shell'].get('init', {}).items():
_obj = context.get(name, import_string(name))
args = spec.get('args', []) if spec else []
kwargs = spec.get('kwargs', {}) if spec else {}
_obj(*args, **kwargs)


def exec_init_script(manage_dict, context):
if 'init_script' in manage_dict['shell']:
exec(manage_dict['shell']['init_script'], context)
93 changes: 56 additions & 37 deletions manage/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,30 @@
# -*- coding: utf-8 -*-
import os
import code
import json
import yaml
import click
import readline
import importlib
import rlcompleter
from manage.template import default_manage_dict

from manage.auto_import import import_objects, exec_init, exec_init_script

MANAGE_FILE = 'manage.yml'
HIDDEN_MANAGE_FILE = '.{0}'.format(MANAGE_FILE)


def load_manage_dict():
if os.path.exists(MANAGE_FILE):
manage_filename = MANAGE_FILE
elif os.path.exists(HIDDEN_MANAGE_FILE):
manage_filename = HIDDEN_MANAGE_FILE
else:
return default_manage_dict
with open(manage_filename) as manage_file:
return yaml.load(manage_file)


if os.path.exists(MANAGE_FILE):
with open(MANAGE_FILE) as manage_file:
manage_dict = yaml.load(manage_file)
else:
manage_dict = default_manage_dict
manage_dict = load_manage_dict()


@click.group()
Expand All @@ -25,59 +34,67 @@ def core_cmd():
pass


class DynamicImporter(object):
def __init__(self, module_name):
self.module_name = module_name

def __getattr__(self, item):
return importlib.import_module(
'{0}.{1}'.format(self.module_name, item))


@core_cmd.command()
@click.option('--banner')
@click.option('--hidden/--no-hidden', default=False)
@click.option('--backup/--no-backup', default=False)
@click.option('--backup/--no-backup', default=True)
def init(banner, hidden, backup):
"""Initialize a manage shell in current directory
$ manage init --banner="My awesome app shell"
initializing manage...
creating manage.yml
"""
manage_file = '.{0}'.format(MANAGE_FILE) if hidden else MANAGE_FILE
manage_file = HIDDEN_MANAGE_FILE if hidden else MANAGE_FILE
if os.path.exists(manage_file):
if not click.confirm('Rewrite {0}?'.format(manage_file)):
return

if backup:
bck = '.bck_{0}'.format(manage_file)
with open(manage_file, 'r') as source, open(bck, 'w') as bck_file:
bck_file.write(source.read())
if backup:
bck = '.bck_{0}'.format(manage_file)
with open(manage_file, 'r') as source, open(bck, 'w') as bck_file:
bck_file.write(source.read())

with open(manage_file, 'w') as output:
data = default_manage_dict
data['shell']['banner']['message'] = banner
output.write(yaml.dump(data, default_flow_style=False))


@core_cmd.command()
def debug():
"""Shows the parsed manage file"""
print(json.dumps(manage_dict, indent=2))


@core_cmd.command()
@click.option('--ipython/--no-ipython', default=True)
def shell(ipython):
"""Runs a Python shell with context"""
_vars = globals()
_vars.update(locals())
auto_imported = {
}
auto_imported = import_objects(manage_dict)
_vars.update(auto_imported)
banner_msg = (
'{banner_message}\n'
'\tAuto imported: {auto_imported}\n'
).format(
auto_imported=auto_imported.keys(),
banner_message=manage_dict['shell']['banner']['message']
)
readline.set_completer(rlcompleter.Completer(_vars).complete)
readline.parse_and_bind('tab: complete')
msgs = []
if manage_dict['shell']['banner']['enabled']:
msgs.append(
manage_dict['shell']['banner']['message'].format(**manage_dict)
)
if manage_dict['shell']['auto_import']['display']:
auto_imported_names = [
key for key in auto_imported.keys()
if key not in ['__builtins__', 'builtins']
]
msgs.append('\tAuto imported: {0}\n'.format(auto_imported_names))

banner_msg = u'\n'.join(msgs)

if manage_dict['shell']['readline_enabled']:
readline.set_completer(rlcompleter.Completer(_vars).complete)
readline.parse_and_bind('tab: complete')

exec_init(manage_dict, _vars)
exec_init_script(manage_dict, _vars)

try:
if ipython is True:
from IPython import start_ipython
Expand All @@ -92,10 +109,12 @@ def shell(ipython):
shell.interact(banner=banner_msg)


help_text = """
PROGRAM Interactive shell!
"""
main = click.CommandCollection(help=help_text)
main = click.CommandCollection(
help=manage_dict.get(
'help_text', '{project_name} Interactive shell!'
).format(**manage_dict)
)

main.add_source(core_cmd)

if __name__ == '__main__':
Expand Down
15 changes: 11 additions & 4 deletions manage/template.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
default_manage_dict = {
'shell': {
'banner': {
'enabled': True,
'message': 'Hello World!'
'project_name': 'Project',
'shell': { # Preferences for 'manage shell'
'banner': { # Banner is the message printed on top of console
'enabled': True, # It can be disabled
'message': 'Hello World!' # Here it goes the message
},
'auto_import': { # Objects to be auto imported to shell context
'display': True, # Weather to print all a list of all objects
'objects': {

}
}
}
}
115 changes: 115 additions & 0 deletions manage/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# coding: utf-8
import sys
import importlib

PY2 = sys.version_info[0] == 2
WIN = sys.platform.startswith('win')


if PY2:
# exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')
def reraise(tp, value, tb=None):
raise tp, value, tb # noqa
else:
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value


def import_string(import_name, silent=False):
"""Imports an object based on a string. This is useful if you want to
use import paths as endpoints or something similar. An import path can
be specified either in dotted notation (``xml.sax.saxutils.escape``)
or with a colon as object delimiter (``xml.sax.saxutils:escape``).
If `silent` is True the return value will be `None` if the import fails.
:param import_name: the dotted name for the object to import.
:param silent: if set to `True` import errors are ignored and
`None` is returned instead.
:return: imported object
"""
# force the import name to automatically convert to strings
# __import__ is not able to handle unicode strings in the fromlist
# if the module is a package
import_name = str(import_name).replace(':', '.')
try:
try:
__import__(import_name)
except ImportError:
if '.' not in import_name:
raise
else:
return sys.modules[import_name]

module_name, obj_name = import_name.rsplit('.', 1)
try:
module = __import__(module_name, None, None, [obj_name])
except ImportError:
# support importing modules not yet set up by the parent module
# (or package for that matter)
module = import_string(module_name)

try:
return getattr(module, obj_name)
except AttributeError as e:
raise ImportError(e)

except ImportError as e:
if not silent:
reraise(
ImportStringError,
ImportStringError(import_name, e),
sys.exc_info()[2])


class ImportStringError(ImportError):

"""Provides information about a failed :func:`import_string` attempt."""

#: String in dotted notation that failed to be imported.
import_name = None
#: Wrapped exception.
exception = None

def __init__(self, import_name, exception):
self.import_name = import_name
self.exception = exception

msg = (
'import_string() failed for %r. Possible reasons are:\n\n'
'- missing __init__.py in a package;\n'
'- package or module path not included in sys.path;\n'
'- duplicated package or module name taking precedence in '
'sys.path;\n'
'- missing module, class, function or variable;\n\n'
'Debugged import:\n\n%s\n\n'
'Original exception:\n\n%s: %s')

name = ''
tracked = []
for part in import_name.replace(':', '.').split('.'):
name += (name and '.') + part
imported = import_string(name, silent=True)
if imported:
tracked.append((name, getattr(imported, '__file__', None)))
else:
track = ['- %r found in %r.' % (n, i) for n, i in tracked]
track.append('- %r not found.' % name)
msg = msg % (import_name, '\n'.join(track),
exception.__class__.__name__, str(exception))
break

ImportError.__init__(self, msg)

def __repr__(self):
return '<%s(%r, %r)>' % (self.__class__.__name__, self.import_name,
self.exception)


class DynamicObjectImporter(object):
def __init__(self, module_name):
self.module_name = module_name

def __getattr__(self, item):
return importlib.import_module(
'{0}.{1}'.format(self.module_name, item))
2 changes: 1 addition & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ watchdog==0.8.3
flake8==2.5.4
tox==2.3.1
coverage==4.1
Sphinx==1.4.3
Sphinx==1.4.4
cryptography==1.4
PyYAML==3.11
pytest==2.9.2

0 comments on commit 3b0e66c

Please sign in to comment.