Skip to content

Commit

Permalink
Improved CLI
Browse files Browse the repository at this point in the history
- removed obsolete code from script pysite
- added txt format for pretty tables (output only)
- made CLI autodetect root_dir: it's the same dir in which the config file is
  Not the best of my decisions, but well...
  • Loading branch information
Dirk Makowski committed Dec 19, 2012
1 parent d6757f5 commit e3a2712
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 154 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var/sessions/lock/*
__pycache__
etc/*secrets.yaml
etc/**/*secrets.yaml
etc/**
downloads
art
build
Expand Down
1 change: 0 additions & 1 deletion etc/Morrigan/rc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# General Settings
# ###########################################


# ###########################################
# Resources
# ###########################################
Expand Down
1 change: 1 addition & 0 deletions pysite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import pysite.security
import pysite.models
import pysite.i18n
import pysite.vmailmgr.manager


def main(global_config, **settings):
Expand Down
40 changes: 38 additions & 2 deletions pysite/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
)
import json
import os
from prettytable import PrettyTable

from pysite.rc import Rc
import pysite.models
Expand Down Expand Up @@ -48,9 +49,11 @@ def init_app(self, args):
if 'environment' not in settings:
raise KeyError('Missing key "environment" in config. Specify '
'environment in paster INI file.')
# The directory of the config file is our root_dir
rc = Rc(environment=settings['environment'],
root_dir=os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..')
root_dir=os.path.normpath(
os.path.join(os.getcwd(), os.path.dirname(
self._args.config))
)
)
rc.load()
Expand Down Expand Up @@ -119,6 +122,8 @@ def _print(self, data):
self._print_json(data)
elif fmt == 'tsv':
self._print_tsv(data)
elif fmt == 'txt':
self._print_txt(data)
else:
self._print_yaml(data)

Expand All @@ -142,6 +147,34 @@ def _print_tsv(self, data):
for row in data:
print("\t".join([str(v) for v in row.values()]))

def _print_txt(self, data):
# We need a list of hh for prettytable, otherwise we get
# TypeError: 'KeysView' object does not support indexing
try:
hh = data[0].keys()
except KeyError: # missing data[0]
# Data was not a list, maybe a dict
hh = data.keys()
t = PrettyTable(list(hh))
t.align = 'l'
t.add_row([data[h] for h in hh])
print(t)
except AttributeError: # missing data.keys()
# Just a simple list
# PrettyTable *must* have column headers and the headers *must*
# be str, not int or else!
t = PrettyTable([ str(i) for i in range(len(data))])
t.align = 'l'
t.add_row(data)
print(t)
else:
# Data is list of dicts (like resultset from DB)
t = PrettyTable(list(hh))
t.align = 'l'
for row in data:
t.add_row([row[h] for h in hh])
print(t)

def _print_yaml(self, data):
yaml.dump(data, sys.stdout, **self.dump_opts_yaml)

Expand All @@ -151,6 +184,9 @@ def _parse(self, data):
return self._parse_json(data)
if fmt == 'tsv':
return self._parse_tsv(data)
if fmt == 'txt':
raise NotImplementedError("Reading data from pretty ASCII tables"
"is not implemented")
else:
return self._parse_yaml(data)

Expand Down
155 changes: 4 additions & 151 deletions pysite/scripts/pysite.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@

import pysite.models
import pysite.lib
import pysite.cli
from pysite.rc import Rc
import pysite.usrmgr.manager as usrmanager
import pysite.sitemgr.manager as sitemanager
Expand All @@ -110,158 +111,10 @@ def _represent_ordereddict(self, data):
yaml.add_representer(OrderedDict, _represent_ordereddict)


class PySiteCli(object):
class PySiteCli(pysite.cli.Cli):

def __init__(self):
self.dump_opts_json = dict(
sort_keys=False,
indent=4,
ensure_ascii=False
)
self.dump_opts_yaml = dict(
allow_unicode=True,
default_flow_style=False
)

def init_app(self, args):
"""
Initialises Pyramid application.
Loads config settings. Initialises SQLAlchemy.
"""
self._args = args
setup_logging(self._args.config)
settings = get_appsettings(self._args.config)

if 'environment' not in settings:
raise KeyError('Missing key "environment" in config. Specify '
'environment in paster INI file.')
rc = Rc(environment=settings['environment'],
root_dir=os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..')
)
)
rc.load()
settings.update(rc.data)
settings['rc'] = rc

pysite.models.init(settings, 'db.pysite.sa.')

self._rc = rc
self._settings = settings

def _db_data_to_list(self, rs, fkmaps=None):
"""
Transmogrifies db data into list including relationships.
We use :func:`~.pysite.models.todict` to turn an entity into a dict, which
will only catch regular field, not foreign keys (relationships).
Parameter ``fkmaps`` is a dict that maps relationship names to functions.
The function must have one input parameter which obtains a reference to
the processed foreign entity. The function then returns the computed value.
E.g.::
class Principal(DbBase):
roles = relationship(Role)
If accessed, member ``roles`` is a list of associated roles. For each role
the mapped function is called and the current role given::
for attr, func in fkmaps.items():
r[attr] = [func(it) for it in getattr(obj, attr)]
And the function is defined like this::
fkmaps=dict(roles=lambda it: it.name)
:param rs: Db resultset, like a list of entities
:param fkmaps: Dict with foreign key mappings
"""
rr = []
for obj in rs:
it = pysite.models.todict(obj)
r = OrderedDict()
r['id'] = it['id']
for k in sorted(it.keys()):
if k in ['id', 'owner', 'ctime', 'editor', 'mtime']:
continue
r[k] = it[k]
for k in ['owner', 'ctime', 'editor', 'mtime']:
try:
r[k] = it[k]
except KeyError:
pass
if fkmaps:
for attr, func in fkmaps.items():
r[attr] = [func(it) for it in getattr(obj, attr)]
rr.append(r)
return rr

def _print(self, data):
fmt = self._args.format.lower()
if fmt == 'json':
self._print_json(data)
elif fmt == 'tsv':
self._print_tsv(data)
else:
self._print_yaml(data)

def _print_json(self, data):
print(json.dumps(data, **self.dump_opts_json))

def _print_tsv(self, data):
try:
hh = data[0].keys()
print("\t".join(hh))
except KeyError: # missing data[0]
# Data was not a list, maybe a dict
hh = data.keys()
print("\t".join(hh))
print("\t".join([str(v) for v in data.values()]))
except AttributeError: # missing data.keys()
# Data is just a list
print("\t".join(data))
else:
# Data is list of dicts (like resultset from DB)
for row in data:
print("\t".join([str(v) for v in row.values()]))

def _print_yaml(self, data):
yaml.dump(data, sys.stdout, **self.dump_opts_yaml)

def _parse(self, data):
fmt = self._args.format.lower()
if fmt == 'json':
return self._parse_json(data)
if fmt == 'tsv':
return self._parse_tsv(data)
else:
return self._parse_yaml(data)

def _parse_json(self, data):
return json.loads(data)

def _parse_tsv(self, data):
data = []
for row in "\n".split(data):
data.append([x.strip() for x in "\t".split(row)])
return data

def _parse_yaml(self, data):
return yaml.load(data)

def _build_query(self, entity):
sess = pysite.models.DbSession()
qry = sess.query(entity)
if self._args.idlist:
qry = qry.filter(entity.id.in_(self._args.idlist))
else:
if self._args.filter:
qry = qry.filter(self._args.filter)
if self._args.order:
qry = qry.order_by(self._args.order)
return qry
super().__init__()

def list_principals(self):
from pysite.usrmgr.models import Principal
Expand Down Expand Up @@ -445,7 +298,7 @@ def main(argv=sys.argv):
help="""Path to INI file with configuration,
e.g. 'production.ini'""")
parser.add_argument('-f', '--format', default='yaml',
choices=['yaml', 'json', 'tsv'],
choices=['yaml', 'json', 'tsv', 'txt'],
help="Set format for input and output")
parser.add_argument('--dry-run', action="store_true",
help="The database changes will be rolled back.")
Expand Down

0 comments on commit e3a2712

Please sign in to comment.