diff --git a/README.rst b/README.rst index 08cac2f..4b3db41 100644 --- a/README.rst +++ b/README.rst @@ -20,14 +20,21 @@ Usage Usage: aq [--profile=] [--region=] [--table-cache-ttl=] [-v] [--debug] - aq [--profile=] [--region=] [--table-cache-ttl=] [-v] [--debug] + aq [--profile=] [--region=] [--table-cache-ttl=] [-v] [--debug] [--format=] + + Sample queries: + aq "select tags->'Name' from ec2_instances" + aq "select count(*) from us_west_1.ec2_instances" Options: --profile= Use a specific profile from your credential file --region= The region to use. Overrides config/env settings --table-cache-ttl= number of seconds to cache the tables before we update them from AWS again [default: 300] + --format= Choose a table, json, yaml, or csv formatter [default: table] + -v, --verbose enable verbose logging + --debug enable debug mode Running ``aq`` without specifying any query will start a REPL to run your queries interactively. diff --git a/aq/__init__.py b/aq/__init__.py index 83ad78f..6ddd83e 100644 --- a/aq/__init__.py +++ b/aq/__init__.py @@ -2,7 +2,7 @@ Usage: aq [--profile=] [--region=] [--table-cache-ttl=] [-v] [--debug] - aq [--profile=] [--region=] [--table-cache-ttl=] [-v] [--debug] + aq [--profile=] [--region=] [--table-cache-ttl=] [-v] [--debug] [--format=] Sample queries: aq "select tags->'Name' from ec2_instances" @@ -13,6 +13,8 @@ --region= The region to use. Overrides config/env settings --table-cache-ttl= number of seconds to cache the tables before we update them from AWS again [default: 300] + --format= Choose a table, json, yaml, or csv formatter [default: table] + -v, --verbose enable verbose logging --debug enable debug mode """ @@ -25,12 +27,12 @@ from aq.engines import BotoSqliteEngine from aq.errors import QueryError -from aq.formatters import TableFormatter +import aq.formatters from aq.logger import initialize_logger from aq.parsers import SelectParser from aq.prompt import AqPrompt -__version__ = '0.1.1' +__version__ = '0.1.2' QueryResult = namedtuple('QueryResult', ('parsed_query', 'query_metadata', 'columns', 'rows')) @@ -44,7 +46,15 @@ def get_parser(options): def get_formatter(options): - return TableFormatter(options) + format = options.get("--format") + if (format == "json"): + return formatters.JsonFormatter(options) + elif (format == "csv"): + return formatters.CsvFormatter(options) + elif (format == "yaml"): + return formatters.YamlFormatter(options) + else: + return formatters.TableFormatter(options) def get_prompt(parser, engine, options): diff --git a/aq/formatters.py b/aq/formatters.py index e131021..7f3681d 100644 --- a/aq/formatters.py +++ b/aq/formatters.py @@ -1,5 +1,8 @@ from tabulate import tabulate - +import io +import json +import csv +import yaml class TableFormatter(object): def __init__(self, options=None): @@ -8,3 +11,58 @@ def __init__(self, options=None): @staticmethod def format(columns, rows): return tabulate(rows, headers=columns, tablefmt='psql', missingval='NULL') + + +# Returns an array of hashes with the columns as the keys +def mixinColumns(columns, rows): + rowsArr = [] + cLen = len(columns) + + for row in rows: + r = {} + + for i in range(0, cLen): + r[columns[i]] = row[i] + + rowsArr.append(r) + + return rowsArr + +class JsonFormatter(object): + def __init__(self, options=None): + self.options = options if options else {} + + @staticmethod + def format(columns, rows): + rowsArr = mixinColumns(columns, rows) + return json.dumps(rowsArr) + + +class YamlFormatter(object): + def __init__(self, options=None): + self.options = options if options else {} + + @staticmethod + def format(columns, rows): + rowsArr = mixinColumns(columns, rows) + return yaml.safe_dump(rowsArr, default_flow_style=False ) + + +class CsvFormatter(object): + def __init__(self, options=None): + self.options = options if options else {} + + @staticmethod + def format(columns, rows): + rowsData = io.BytesIO() + writer = csv.DictWriter(rowsData, fieldnames=columns) + writer.writeheader() + + rowsArr = mixinColumns(columns, rows) + for row in rowsArr: + writer.writerow(row) + + # Force string. + # Python2.7 getvalue() returns string + # Python 3.x returns bytes + return str(rowsData.getvalue()) diff --git a/setup.py b/setup.py index 03c88e8..96e4f73 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.1.1', + version='0.1.2', description='Query AWS resources with SQL', long_description=long_description, @@ -75,6 +75,7 @@ 'prompt_toolkit', 'pygments', 'six', + 'pyyaml' ], # List additional groups of dependencies here (e.g. development