From 7c79c7dea3209b62453657c7e2e613e07a50822d Mon Sep 17 00:00:00 2001 From: Berton Earnshaw Date: Mon, 10 Mar 2014 11:52:03 +0800 Subject: [PATCH 1/5] Move package location --- setup.py | 3 +-- {src/sql => sql}/__init__.py | 0 {src/sql => sql}/column_guesser.py | 0 {src/sql => sql}/connection.py | 0 {src/sql => sql}/magic.py | 0 {src/sql => sql}/parse.py | 0 {src/sql => sql}/run.py | 0 {src/tests => tests}/run_tests.sh | 0 {src/tests => tests}/test_column_guesser.py | 0 {src/tests => tests}/test_magic.py | 0 {src/tests => tests}/test_parse.py | 0 11 files changed, 1 insertion(+), 2 deletions(-) rename {src/sql => sql}/__init__.py (100%) rename {src/sql => sql}/column_guesser.py (100%) rename {src/sql => sql}/connection.py (100%) rename {src/sql => sql}/magic.py (100%) rename {src/sql => sql}/parse.py (100%) rename {src/sql => sql}/run.py (100%) rename {src/tests => tests}/run_tests.sh (100%) rename {src/tests => tests}/test_column_guesser.py (100%) rename {src/tests => tests}/test_magic.py (100%) rename {src/tests => tests}/test_parse.py (100%) diff --git a/setup.py b/setup.py index e38a9a09d..2cb27f517 100644 --- a/setup.py +++ b/setup.py @@ -34,8 +34,7 @@ author_email='catherine.devlin@gmail.com', url='pypi.python.org/pypi/ipython-sql', license='MIT', - packages=find_packages('src'), - package_dir = {'': 'src'}, + packages=['sql'], include_package_data=True, zip_safe=False, install_requires=install_requires, diff --git a/src/sql/__init__.py b/sql/__init__.py similarity index 100% rename from src/sql/__init__.py rename to sql/__init__.py diff --git a/src/sql/column_guesser.py b/sql/column_guesser.py similarity index 100% rename from src/sql/column_guesser.py rename to sql/column_guesser.py diff --git a/src/sql/connection.py b/sql/connection.py similarity index 100% rename from src/sql/connection.py rename to sql/connection.py diff --git a/src/sql/magic.py b/sql/magic.py similarity index 100% rename from src/sql/magic.py rename to sql/magic.py diff --git a/src/sql/parse.py b/sql/parse.py similarity index 100% rename from src/sql/parse.py rename to sql/parse.py diff --git a/src/sql/run.py b/sql/run.py similarity index 100% rename from src/sql/run.py rename to sql/run.py diff --git a/src/tests/run_tests.sh b/tests/run_tests.sh similarity index 100% rename from src/tests/run_tests.sh rename to tests/run_tests.sh diff --git a/src/tests/test_column_guesser.py b/tests/test_column_guesser.py similarity index 100% rename from src/tests/test_column_guesser.py rename to tests/test_column_guesser.py diff --git a/src/tests/test_magic.py b/tests/test_magic.py similarity index 100% rename from src/tests/test_magic.py rename to tests/test_magic.py diff --git a/src/tests/test_parse.py b/tests/test_parse.py similarity index 100% rename from src/tests/test_parse.py rename to tests/test_parse.py From 6fad6310593104e008650119f2cbe54c8f66207e Mon Sep 17 00:00:00 2001 From: Berton Earnshaw Date: Mon, 24 Mar 2014 18:11:42 -0600 Subject: [PATCH 2/5] Allow for query arguments in connection string --- sql/connection.py | 2 +- sql/parse.py | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/sql/connection.py b/sql/connection.py index 48a19db42..98e393c2b 100644 --- a/sql/connection.py +++ b/sql/connection.py @@ -25,7 +25,7 @@ def get(cls, descriptor): cls.current = descriptor elif descriptor: conn = cls.connections.get(descriptor) or \ - cls.connections.get(descriptor.lower()) + (type(descriptor) == str and cls.connections.get(descriptor.lower())) if conn: cls.current = conn else: diff --git a/sql/parse.py b/sql/parse.py index bd2afcd2b..294e910ea 100644 --- a/sql/parse.py +++ b/sql/parse.py @@ -1,5 +1,6 @@ from ConfigParser import ConfigParser from sqlalchemy.engine.url import URL +from ast import literal_eval def parse(cell, config): @@ -11,17 +12,19 @@ def parse(cell, config): parser = ConfigParser() parser.read(config.dsn_filename) cfg_dict = dict(parser.items(section)) + if 'query' in cfg_dict.keys(): + cfg_dict['query'] = literal_eval(cfg_dict['query']) - connection = str(URL(**cfg_dict)) - sql = parts[1] if len(parts) > 1 else '' + connection = URL(**cfg_dict) + sql = parts[1].strip() if len(parts) > 1 else '' elif '@' in parts[0] or '://' in parts[0]: - connection = parts[0] + connection = parts[0].strip() if len(parts) > 1: - sql = parts[1] + sql = parts[1].strip() else: sql = '' else: connection = '' - sql = cell - return {'connection': connection.strip(), - 'sql': sql.strip()} + sql = cell.strip() + return {'connection': connection, + 'sql': sql} From d93c1c7bc4ec44304b7044d869de521e66a5b584 Mon Sep 17 00:00:00 2001 From: Berton Earnshaw Date: Mon, 11 Aug 2014 17:39:31 -0600 Subject: [PATCH 3/5] Remove whitespace --- sql/connection.py | 4 ++-- sql/run.py | 54 +++++++++++++++++++++++------------------------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/sql/connection.py b/sql/connection.py index 98e393c2b..2e11bcb77 100644 --- a/sql/connection.py +++ b/sql/connection.py @@ -12,10 +12,10 @@ def __init__(self, connect_str=None): engine = sqlalchemy.create_engine(connect_str) except: # TODO: bare except; but what's an ArgumentError? print(self.tell_format()) - raise + raise self.metadata = sqlalchemy.MetaData(bind=engine) self.name = self.assign_name(engine) - self.session = engine.connect() + self.session = engine.connect() self.connections[self.name] = self self.connections[str(self.metadata.bind.url)] = self Connection.current = self diff --git a/sql/run.py b/sql/run.py index 3f2b719a5..28133c7c6 100644 --- a/sql/run.py +++ b/sql/run.py @@ -64,11 +64,11 @@ def __repr__(self): return 'CSV results at %s' % os.path.join(os.path.abspath('.'), self.file_path) def _repr_html_(self): return 'CSV results' % os.path.join('.', 'files', self.file_path) - + class ResultSet(list, ColumnGuesserMixin): """ Results of a SQL query. - + Can access rows listwise, or by string value of leftmost column. """ def __init__(self, sqlaproxy, sql, config): @@ -124,23 +124,23 @@ def DataFrame(self): return frame def pie(self, key_word_sep=" ", title=None, **kwargs): """Generates a pylab pie chart from the result set. - + ``matplotlib`` must be installed, and in an IPython Notebook, inlining must be on:: - + %%matplotlib inline - - Values (pie slice sizes) are taken from the + + Values (pie slice sizes) are taken from the rightmost column (numerical values required). All other columns are used to label the pie slices. - + Parameters ---------- key_word_sep: string used to separate column values from each other in pie labels title: Plot title, defaults to name of value column - Any additional keyword arguments will be passsed + Any additional keyword arguments will be passsed through to ``matplotlib.pylab.pie``. """ self.guess_pie_columns(xlabel_sep=key_word_sep) @@ -148,23 +148,23 @@ def pie(self, key_word_sep=" ", title=None, **kwargs): pie = plt.pie(self.ys[0], labels=self.xlabels, **kwargs) plt.title(title or self.ys[0].name) return pie - + def plot(self, title=None, **kwargs): """Generates a pylab plot from the result set. - + ``matplotlib`` must be installed, and in an IPython Notebook, inlining must be on:: - + %%matplotlib inline - + The first and last columns are taken as the X and Y values. Any columns between are ignored. - + Parameters ---------- title: Plot title, defaults to names of Y value columns - Any additional keyword arguments will be passsed + Any additional keyword arguments will be passsed through to ``matplotlib.pylab.plot``. """ import matplotlib.pylab as plt @@ -178,25 +178,25 @@ def plot(self, title=None, **kwargs): plt.title(title or ylabel) plt.ylabel(ylabel) return plot - + def bar(self, key_word_sep = " ", title=None, **kwargs): """Generates a pylab bar plot from the result set. - + ``matplotlib`` must be installed, and in an IPython Notebook, inlining must be on:: - + %%matplotlib inline - + The last quantitative column is taken as the Y values; - all other columns are combined to label the X axis. - + all other columns are combined to label the X axis. + Parameters ---------- title: Plot title, defaults to names of Y value columns key_word_sep: string used to separate column values from each other in labels - - Any additional keyword arguments will be passsed + + Any additional keyword arguments will be passsed through to ``matplotlib.pylab.bar``. """ import matplotlib.pylab as plt @@ -207,8 +207,8 @@ def bar(self, key_word_sep = " ", title=None, **kwargs): rotation=45) plt.xlabel(self.xlabel) plt.ylabel(self.ys[0].name) - return plot - + return plot + def csv(self, filename=None, **format_params): """Generate results in comma-separated form. Write to ``filename`` if given. Any other parameterw will be passed on to csv.writer.""" @@ -227,8 +227,8 @@ def csv(self, filename=None, **format_params): return CsvResultDescriptor(filename) else: return outfile.getvalue() - - + + def interpret_rowcount(rowcount): if rowcount < 0: result = 'Done.' @@ -252,4 +252,4 @@ def run(conn, sql, config, user_namespace): #returning only last result, intentionally else: return 'Connected: %s' % conn.name - \ No newline at end of file + From 9fb1a6ef4e8e4aaedeecf6f3fa935d562378918a Mon Sep 17 00:00:00 2001 From: Berton Earnshaw Date: Mon, 11 Aug 2014 17:40:09 -0600 Subject: [PATCH 4/5] Improve formatting of config help statements --- sql/magic.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sql/magic.py b/sql/magic.py index 40ae86f24..9f230bf01 100644 --- a/sql/magic.py +++ b/sql/magic.py @@ -18,13 +18,13 @@ class SqlMagic(Magics, Configurable): autolimit = Int(0, config=True, help="Automatically limit the size of the returned result sets") style = Unicode('DEFAULT', config=True, help="Set the table printing style to any of prettytable's defined styles (currently DEFAULT, MSWORD_FRIENDLY, PLAIN_COLUMNS, RANDOM)") short_errors = Bool(True, config=True, help="Don't display the full traceback on SQL Programming Error") - displaylimit = Int(0, config=True, help="Automatic,ally limit the number of rows displayed (full result set is still stored)") + displaylimit = Int(0, config=True, help="Automatically limit the number of rows displayed (full result set is still stored)") autopandas = Bool(False, config=True, help="Return Pandas DataFrames instead of regular result sets") feedback = Bool(True, config=True, help="Print number of rows affected by DML") - dsn_filename = Unicode('odbc.ini', config=True, help="Path to DSN file. \ - When the first argument is of the form [section], \ - a sqlalchemy connection string is formed from the \ - matching section in the DSN file.") + dsn_filename = Unicode('odbc.ini', config=True, help='Path to DSN file.' \ + ' When the first argument is of the form [section],' \ + ' a sqlalchemy connection string is formed from the' \ + ' matching section in the DSN file.') def __init__(self, shell): Configurable.__init__(self, config=shell.config) From 34e5328c6b1f5f530898fe956f96e55d45f86668 Mon Sep 17 00:00:00 2001 From: Berton Earnshaw Date: Mon, 11 Aug 2014 17:42:27 -0600 Subject: [PATCH 5/5] WIP Add iterations over columns --- sql/magic.py | 2 +- sql/parse.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/sql/magic.py b/sql/magic.py index 9f230bf01..64297f40d 100644 --- a/sql/magic.py +++ b/sql/magic.py @@ -65,7 +65,7 @@ def execute(self, line, cell='', local_ns={}): user_ns = self.shell.user_ns user_ns.update(local_ns) - parsed = sql.parse.parse('%s\n%s' % (line, cell), self) + parsed = sql.parse.parse('%s\n%s' % (line, cell), self, user_ns) conn = sql.connection.Connection.get(parsed['connection']) try: result = sql.run.run(conn, parsed['sql'], self, user_ns) diff --git a/sql/parse.py b/sql/parse.py index 294e910ea..ca248cfd5 100644 --- a/sql/parse.py +++ b/sql/parse.py @@ -3,20 +3,29 @@ from ast import literal_eval -def parse(cell, config): +def parse(cell, config, user_ns={}): parts = [part.strip() for part in cell.split(None, 1)] if not parts: return {'connection': '', 'sql': ''} if parts[0].startswith('[') and parts[0].endswith(']'): - section = parts[0].lstrip('[').rstrip(']') + part0 = parts[0].lstrip('[').rstrip(']').split(',') + section = part0[0] + var_names = part0[1:] parser = ConfigParser() parser.read(config.dsn_filename) cfg_dict = dict(parser.items(section)) if 'query' in cfg_dict.keys(): cfg_dict['query'] = literal_eval(cfg_dict['query']) - connection = URL(**cfg_dict) + # TODO currently the url has to be turned into a string object + # so that the Connection object can find it in its dictionary + connection = str(URL(**cfg_dict)) sql = parts[1].strip() if len(parts) > 1 else '' + for var_name in var_names or []: + var_name = var_name.strip() + var_val = user_ns.get(var_name) + if var_val: + sql = sql.replace(':{}'.format(var_name), str(var_val)) elif '@' in parts[0] or '://' in parts[0]: connection = parts[0].strip() if len(parts) > 1: