summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md2
-rw-r--r--.travis.yml1
-rw-r--r--AUTHORS2
-rw-r--r--DEVELOP.rst8
-rw-r--r--README.rst4
-rw-r--r--changelog.rst12
-rw-r--r--pgcli/main.py90
-rw-r--r--pgcli/packages/sqlcompletion.py4
-rw-r--r--pgcli/pgcompleter.py15
-rw-r--r--pgcli/pgexecute.py3
-rw-r--r--setup.py1
11 files changed, 94 insertions, 48 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 8d498abc..d02bf3b6 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -5,5 +5,5 @@
## Checklist
<!--- We appreciate your help and want to give you credit. Please take a moment to put an `x` in the boxes below as you complete them. -->
-- [ ] I've added this contribution to the `changelog.md`.
+- [ ] I've added this contribution to the `changelog.rst`.
- [ ] I've added my name to the `AUTHORS` file (or it's already there).
diff --git a/.travis.yml b/.travis.yml
index 6093e745..f54e1a8e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,6 +8,7 @@ python:
install:
- pip install .
- pip install -r requirements-dev.txt
+ - pip install keyrings.alt>=3.1
script:
- set -e
diff --git a/AUTHORS b/AUTHORS
index 611d40c5..74bf99d5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -76,6 +76,8 @@ Contributors:
* Pierre Giraud
* Andrew Kuchling
* Dan Clark
+ * Catherine Devlin
+ * Jason Ribeiro
Creator:
diff --git a/DEVELOP.rst b/DEVELOP.rst
index 504ad3af..601c3f30 100644
--- a/DEVELOP.rst
+++ b/DEVELOP.rst
@@ -148,8 +148,8 @@ To see stdout/stderr, use the following command:
$ behave --no-capture
-PEP8 checks
------------
+PEP8 checks (lint)
+-----------------_
When you submit a PR, the changeset is checked for pep8 compliance using
`pep8radius <https://github.com/hayd/pep8radius>`_. If you see a build failing because
@@ -158,7 +158,7 @@ of these checks, install pep8radius and apply style fixes:
::
$ pip install pep8radius
- $ pep8radius --docformatter --diff # view a diff of proposed fixes
- $ pep8radius --docformatter --in-place # apply the fixes
+ $ pep8radius master --docformatter --diff # view a diff of proposed fixes
+ $ pep8radius master --docformatter --in-place # apply the fixes
Then commit and push the fixes.
diff --git a/README.rst b/README.rst
index bf5cbd64..58569fcf 100644
--- a/README.rst
+++ b/README.rst
@@ -48,7 +48,7 @@ Usage
or
- $ pgcli postgresql://[user[:password]@][netloc][:port][/dbname]
+ $ pgcli postgresql://[user[:password]@][netloc][:port][/dbname][?extra=value[&other=other-value]]
Examples:
@@ -56,7 +56,7 @@ Examples:
$ pgcli local_database
- $ pgcli postgres://amjith:pa$$w0rd@example.com:5432/app_db
+ $ pgcli postgres://amjith:pa$$w0rd@example.com:5432/app_db?sslmode=verify-ca&sslrootcert=/myrootcert
Features
--------
diff --git a/changelog.rst b/changelog.rst
index dc4c8d6d..0be97c6a 100644
--- a/changelog.rst
+++ b/changelog.rst
@@ -1,6 +1,11 @@
Upcoming:
=========
+Features:
+---------
+* Add quit commands to the completion menu. (Thanks: `Jason Ribeiro`_)
+* Add table formats to ``\T`` completion. (Thanks: `Jason Ribeiro`_)
+
Internal changes:
-----------------
@@ -8,6 +13,11 @@ Internal changes:
* Add ``application_name`` to help identify pgcli connection to database (issue #868) (Thanks: `François Pietka`_)
* Ported Destructive Warning from mycli.
+Bug Fixes:
+----------
+* Disable pager when using \watch (#837). (Thanks: `Jason Ribeiro`_)
+* Don't offer to reconnect when we can't change a param in realtime (#807). (Thanks: `Amjith Ramanujam`_)
+
1.9.1:
======
@@ -746,6 +756,7 @@ Bug Fixes:
----------
* Fix the broken behavior of \d+. (Thanks: https://github.com/macobo)
* Fix a crash during auto-completion. (Thanks: https://github.com/Erethon)
+* Avoid losing pre_run_callables on error in editing. (Thanks: https://github.com/catherinedevlin)
Improvements:
-------------
@@ -814,3 +825,4 @@ Improvements:
.. _`Isank`: https://github.com/isank
.. _`Bojan Delić`: https://github.com/delicb
.. _`Frederic Aoustin`: https://github.com/fraoustin
+.. _`Jason Ribeiro`: https://github.com/jrib
diff --git a/pgcli/main.py b/pgcli/main.py
index b0096dc4..1ce3f4b0 100644
--- a/pgcli/main.py
+++ b/pgcli/main.py
@@ -40,6 +40,7 @@ from pygments.token import Token
from pgspecial.main import (PGSpecial, NO_QUERY, PAGER_OFF)
import pgspecial as special
+import keyring
from .pgcompleter import PGCompleter
from .pgtoolbar import create_toolbar_tokens_func
from .pgstyle import style_factory, style_factory_output
@@ -91,6 +92,10 @@ OutputSettings.__new__.__defaults__ = (
)
+class PgCliQuitError(Exception):
+ pass
+
+
class PGCli(object):
default_prompt = '\\u@\\h:\\d> '
@@ -202,6 +207,9 @@ class PGCli(object):
self.eventloop = create_eventloop()
self.cli = None
+ def quit(self):
+ raise PgCliQuitError
+
def register_special_commands(self):
self.pgspecial.register(
@@ -211,6 +219,12 @@ class PGCli(object):
refresh_callback = lambda: self.refresh_completions(
persist_priorities='all')
+ self.pgspecial.register(self.quit, '\\q', '\\q',
+ 'Quit pgcli.', arg_type=NO_QUERY, case_sensitive=True,
+ aliases=(':q',))
+ self.pgspecial.register(self.quit, 'quit', 'quit',
+ 'Quit pgcli.', arg_type=NO_QUERY, case_sensitive=False,
+ aliases=('exit',))
self.pgspecial.register(refresh_callback, '\\#', '\\#',
'Refresh auto-completions.', arg_type=NO_QUERY)
self.pgspecial.register(refresh_callback, '\\refresh', '\\refresh',
@@ -367,7 +381,7 @@ class PGCli(object):
user=fixup_possible_percent_encoding(uri.username),
port=fixup_possible_percent_encoding(uri.port),
passwd=fixup_possible_percent_encoding(uri.password))
- # Deal with extra params e.g. ?sslmode=verify-ca&ssl-cert=/mycert
+ # Deal with extra params e.g. ?sslmode=verify-ca&sslrootcert=/myrootcert
if uri.query:
arguments = dict(
{k: v for k, (v,) in parse_qs(uri.query).items()},
@@ -391,6 +405,11 @@ class PGCli(object):
if not self.force_passwd_prompt and not passwd:
passwd = os.environ.get('PGPASSWORD', '')
+ # Find password from store
+ key = '%s@%s' % (user, host)
+ if not passwd:
+ passwd = keyring.get_password('pgcli', key)
+
# Prompt for a password immediately if requested via the -W flag. This
# avoids wasting time trying to connect to the database and catching a
# no-password exception.
@@ -412,6 +431,8 @@ class PGCli(object):
try:
pgexecute = PGExecute(database, user, passwd, host, port, dsn,
application_name='pgcli', **kwargs)
+ if passwd:
+ keyring.set_password('pgcli', key, passwd)
except (OperationalError, InterfaceError) as e:
if ('no password supplied' in utf8tounicode(e.args[0]) and
auto_passwd_prompt):
@@ -421,6 +442,8 @@ class PGCli(object):
pgexecute = PGExecute(database, user, passwd, host, port,
dsn, application_name='pgcli',
**kwargs)
+ if passwd:
+ keyring.set_password('pgcli', key, passwd)
else:
raise e
@@ -449,19 +472,22 @@ class PGCli(object):
# It's internal api of prompt_toolkit that may change. This was added to fix #668.
# We may find a better way to do it in the future.
saved_callables = cli.application.pre_run_callables
- while special.editor_command(document.text):
- filename = special.get_filename(document.text)
- query = (special.get_editor_query(document.text) or
- self.get_last_query())
- sql, message = special.open_external_editor(filename, sql=query)
- if message:
- # Something went wrong. Raise an exception and bail.
- raise RuntimeError(message)
- cli.current_buffer.document = Document(sql, cursor_position=len(sql))
- cli.application.pre_run_callables = []
- document = cli.run()
- continue
- cli.application.pre_run_callables = saved_callables
+ try:
+ while special.editor_command(document.text):
+ filename = special.get_filename(document.text)
+ query = (special.get_editor_query(document.text) or
+ self.get_last_query())
+ sql, message = special.open_external_editor(
+ filename, sql=query)
+ if message:
+ # Something went wrong. Raise an exception and bail.
+ raise RuntimeError(message)
+ cli.current_buffer.document = Document(sql,
+ cursor_position=len(sql))
+ cli.application.pre_run_callables = []
+ document = cli.run()
+ finally:
+ cli.application.pre_run_callables = saved_callables
return document
def execute_command(self, text, query):
@@ -489,6 +515,8 @@ class PGCli(object):
logger.error("sql: %r, error: %r", text, e)
logger.error("traceback: %r", traceback.format_exc())
self._handle_server_closed_connection()
+ except PgCliQuitError as e:
+ raise
except Exception as e:
logger.error("sql: %r, error: %r", text, e)
logger.error("traceback: %r", traceback.format_exc())
@@ -555,13 +583,6 @@ class PGCli(object):
while True:
document = self.cli.run()
- # The reason we check here instead of inside the pgexecute is
- # because we want to raise the Exit exception which will be
- # caught by the try/except block that wraps the pgexecute.run()
- # statement.
- if quit_command(document.text):
- raise EOFError
-
try:
document = self.handle_editor_command(self.cli, document)
except RuntimeError as e:
@@ -573,15 +594,19 @@ class PGCli(object):
# Initialize default metaquery in case execution fails
query = MetaQuery(query=document.text, successful=False)
- watch_command, timing = special.get_watch_command(document.text)
- if watch_command:
- while watch_command:
+ self.watch_command, timing = special.get_watch_command(
+ document.text)
+ if self.watch_command:
+ while self.watch_command:
try:
- query = self.execute_command(watch_command, query)
- click.echo('Waiting for {0} seconds before repeating'.format(timing))
+ query = self.execute_command(
+ self.watch_command, query)
+ click.echo(
+ 'Waiting for {0} seconds before repeating'
+ .format(timing))
sleep(timing)
except KeyboardInterrupt:
- watch_command = None
+ self.watch_command = None
else:
query = self.execute_command(document.text, query)
@@ -593,7 +618,7 @@ class PGCli(object):
self.query_history.append(query)
- except EOFError:
+ except PgCliQuitError:
if not self.less_chatty:
print ('Goodbye!')
@@ -849,7 +874,7 @@ class PGCli(object):
return self.query_history[-1][0] if self.query_history else None
def echo_via_pager(self, text, color=None):
- if self.pgspecial.pager_config == PAGER_OFF:
+ if self.pgspecial.pager_config == PAGER_OFF or self.watch_command:
click.echo(text, color=color)
else:
click.echo_via_pager(text, color)
@@ -1044,13 +1069,6 @@ def is_select(status):
return status.split(None, 1)[0].lower() == 'select'
-def quit_command(sql):
- return (sql.strip().lower() == 'exit'
- or sql.strip().lower() == 'quit'
- or sql.strip() == r'\q'
- or sql.strip() == ':q')
-
-
def exception_formatter(e):
return click.style(utf8tounicode(str(e)), fg='red')
diff --git a/pgcli/packages/sqlcompletion.py b/pgcli/packages/sqlcompletion.py
index f92a8a71..aa829853 100644
--- a/pgcli/packages/sqlcompletion.py
+++ b/pgcli/packages/sqlcompletion.py
@@ -28,6 +28,7 @@ Schema.__new__.__defaults__ = (False,)
# used to ensure that the alias we suggest is unique
FromClauseItem = namedtuple('FromClauseItem', 'schema table_refs local_tables')
Table = namedtuple('Table', ['schema', 'table_refs', 'local_tables'])
+TableFormat = namedtuple('TableFormat', [])
View = namedtuple('View', ['schema', 'table_refs'])
# JoinConditions are suggested after ON, e.g. 'foo.barid = bar.barid'
JoinCondition = namedtuple('JoinCondition', ['table_refs', 'parent'])
@@ -252,6 +253,9 @@ def suggest_special(text):
if cmd in ('\\c', '\\connect'):
return (Database(),)
+ if cmd == '\\T':
+ return (TableFormat(),)
+
if cmd == '\\dn':
return (Schema(),)
diff --git a/pgcli/pgcompleter.py b/pgcli/pgcompleter.py
index cba9fb7c..72d54d82 100644
--- a/pgcli/pgcompleter.py
+++ b/pgcli/pgcompleter.py
@@ -4,13 +4,15 @@ import re
from itertools import count, repeat, chain
import operator
from collections import namedtuple, defaultdict, OrderedDict
+from cli_helpers.tabular_output import TabularOutputFormatter
from pgspecial.namedqueries import NamedQueries
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.contrib.completers import PathCompleter
from prompt_toolkit.document import Document
-from .packages.sqlcompletion import (FromClauseItem,
- suggest_type, Special, Database, Schema, Table, Function, Column, View,
- Keyword, NamedQuery, Datatype, Alias, Path, JoinCondition, Join)
+from .packages.sqlcompletion import (
+ FromClauseItem, suggest_type, Special, Database, Schema, Table,
+ TableFormat, Function, Column, View, Keyword, NamedQuery,
+ Datatype, Alias, Path, JoinCondition, Join)
from .packages.parseutils.meta import ColumnMetadata, ForeignKey
from .packages.parseutils.utils import last_word
from .packages.parseutils.tables import TableReference
@@ -321,7 +323,8 @@ class PGCompleter(Completer):
return []
prio_order = [
'keyword', 'function', 'view', 'table', 'datatype', 'database',
- 'schema', 'column', 'table alias', 'join', 'name join', 'fk join'
+ 'schema', 'column', 'table alias', 'join', 'name join', 'fk join',
+ 'table format'
]
type_priority = prio_order.index(meta) if meta in prio_order else -1
text = last_word(text, include='most_punctuations').lower()
@@ -778,6 +781,9 @@ class PGCompleter(Completer):
tables = [self._make_cand(t, alias, suggestion) for t in tables]
return self.find_matches(word_before_cursor, tables, meta='table')
+ def get_table_formats(self, _, word_before_cursor):
+ formats = TabularOutputFormatter().supported_formats
+ return self.find_matches(word_before_cursor, formats, meta='table format')
def get_view_matches(self, suggestion, word_before_cursor, alias=False):
views = self.populate_schema_objects(suggestion.schema, 'views')
@@ -861,6 +867,7 @@ class PGCompleter(Completer):
Function: get_function_matches,
Schema: get_schema_matches,
Table: get_table_matches,
+ TableFormat: get_table_formats,
View: get_view_matches,
Alias: get_alias_matches,
Database: get_database_matches,
diff --git a/pgcli/pgexecute.py b/pgcli/pgexecute.py
index 74084fe6..2158f909 100644
--- a/pgcli/pgexecute.py
+++ b/pgcli/pgexecute.py
@@ -346,7 +346,8 @@ class PGExecute(object):
"""
return (isinstance(e, psycopg2.OperationalError) and
(not e.pgcode or
- psycopg2.errorcodes.lookup(e.pgcode) != 'LOCK_NOT_AVAILABLE'))
+ psycopg2.errorcodes.lookup(e.pgcode) not in
+ ('LOCK_NOT_AVAILABLE', 'CANT_CHANGE_RUNTIME_PARAM')))
def execute_normal_sql(self, split_sql):
"""Returns tuple (title, rows, headers, status)"""
diff --git a/setup.py b/setup.py
index 84489924..25849aea 100644
--- a/setup.py
+++ b/setup.py
@@ -21,6 +21,7 @@ install_requirements = [
'configobj >= 5.0.6',
'humanize >= 0.5.1',
'cli_helpers[styles] >= 1.0.1',
+ 'keyring >= 12.2.0'
]