Skip to content

Commit

Permalink
Merge branch 'main' into fix/use_confirm_instead_of_prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
j-bennet authored Oct 12, 2023
2 parents 6e13409 + 6332e18 commit a292c14
Show file tree
Hide file tree
Showing 19 changed files with 288 additions and 32 deletions.
11 changes: 7 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: pgcli

on:
push:
branches:
- main
pull_request:
paths-ignore:
- '**.rst'
Expand All @@ -11,7 +14,7 @@ jobs:

strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

services:
postgres:
Expand All @@ -28,10 +31,10 @@ jobs:
--health-retries 5
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

Expand Down Expand Up @@ -86,7 +89,7 @@ jobs:

- name: Run Black
run: black --check .
if: matrix.python-version == '3.7'
if: matrix.python-version == '3.8'

- name: Coverage
run: |
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
repos:
- repo: https://github.com/psf/black
rev: 22.3.0
rev: 23.3.0
hooks:
- id: black
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ Contributors:
* Anna Glasgall (annathyst)
* Andy Schoenberger (andyscho)
* Damien Baty (dbaty)
* blag
* Rob Berry (rob-b)
* Sharon Yogev (sharonyogev)

Creator:
Expand Down
12 changes: 6 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,9 @@ get this running in a development setup.

https://github.com/dbcli/pgcli/blob/master/DEVELOP.rst

Please feel free to reach out to me if you need help.
My email: [email protected], Twitter: `@amjithr <http://twitter.com/amjithr>`_
Please feel free to reach out to us if you need help.
* Amjith, pgcli author: [email protected], Twitter: `@amjithr <http://twitter.com/amjithr>`_
* Irina, pgcli maintainer: [email protected], Twitter: `@amjithr <http://twitter.com/irinatruong>`_

Detailed Installation Instructions:
-----------------------------------
Expand Down Expand Up @@ -351,8 +352,7 @@ choice:

In [3]: my_result = _

Pgcli only runs on Python3.7+ since 4.0.0, if you use an old version of Python,
you should use install ``pgcli <= 4.0.0``.
Pgcli dropped support for Python<3.8 as of 4.0.0. If you need it, install ``pgcli <= 4.0.0``.

Thanks:
-------
Expand All @@ -372,8 +372,8 @@ interface to Postgres database.
Thanks to all the beta testers and contributors for your time and patience. :)


.. |Build Status| image:: https://github.com/dbcli/pgcli/workflows/pgcli/badge.svg
:target: https://github.com/dbcli/pgcli/actions?query=workflow%3Apgcli
.. |Build Status| image:: https://github.com/dbcli/pgcli/actions/workflows/ci.yml/badge.svg?branch=main
:target: https://github.com/dbcli/pgcli/actions/workflows/ci.yml

.. |CodeCov| image:: https://codecov.io/gh/dbcli/pgcli/branch/master/graph/badge.svg
:target: https://codecov.io/gh/dbcli/pgcli
Expand Down
14 changes: 13 additions & 1 deletion changelog.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
========
Upcoming
========

Features:
---------

* Ask for confirmation when quitting cli while a transaction is ongoing.
* New `destructive_statements_require_transaction` config option to refuse to execute a
destructive SQL statement if outside a transaction. This option is off by default.
* Changed the `destructive_warning` config to be a list of commands that are considered
Expand All @@ -16,16 +18,26 @@ Features:
it will now not restart.
* Config option to always run with a single connection.
* Add comment explaining default LESS environment variable behavior and change example pager setting.
* Added `\echo` & `\qecho` special commands. ([issue 1335](https://github.com/dbcli/pgcli/issues/1335)).

Bug fixes:
----------

* Fix \ev not producing a correctly quoted "schema"."view"
* Fix `\ev` not producing a correctly quoted "schema"."view"
* Fix 'invalid connection option "dsn"' ([issue 1373](https://github.com/dbcli/pgcli/issues/1373)).
* Fix explain mode when used with `expand`, `auto_expand`, or `--explain-vertical-output` ([issue 1393](https://github.com/dbcli/pgcli/issues/1393)).
* Fix sql-insert format emits NULL as 'None' ([issue 1408](https://github.com/dbcli/pgcli/issues/1408)).
* Improve check for prompt-toolkit 3.0.6 ([issue 1416](https://github.com/dbcli/pgcli/issues/1416)).
* Allow specifying an `alias_map_file` in the config that will use
predetermined table aliases instead of generating aliases programmatically on
the fly
* Fix wrong usage of prompt instead of confirm when confirm execution of destructive query

Internal:
---------

* Drop support for Python 3.7 and add 3.12.

3.5.0 (2022/09/15):
===================

Expand Down
2 changes: 1 addition & 1 deletion pgcli/completion_refresher.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def refresh(self, executor, special, callbacks, history=None, settings=None):
args=(executor, special, callbacks, history, settings),
name="completion_refresh",
)
self._completer_thread.setDaemon(True)
self._completer_thread.daemon = True
self._completer_thread.start()
return [
(None, None, None, "Auto-completion refresh started in the background.")
Expand Down
61 changes: 59 additions & 2 deletions pgcli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ def __init__(
"single_connection": single_connection,
"less_chatty": less_chatty,
"keyword_casing": keyword_casing,
"alias_map_file": c["main"]["alias_map_file"] or None,
}

completer = PGCompleter(
Expand Down Expand Up @@ -363,6 +364,23 @@ def register_special_commands(self):
"Change the table format used to output results",
)

self.pgspecial.register(
self.echo,
"\\echo",
"\\echo [string]",
"Echo a string to stdout",
)

self.pgspecial.register(
self.echo,
"\\qecho",
"\\qecho [string]",
"Echo a string to the query output channel.",
)

def echo(self, pattern, **_):
return [(None, None, None, pattern)]

def change_table_format(self, pattern, **_):
try:
if pattern not in TabularOutputFormatter().supported_formats:
Expand Down Expand Up @@ -756,7 +774,9 @@ def execute_command(self, text, handle_closed_connection=True):
click.secho(str(e), err=True, fg="red")
else:
try:
if self.output_file and not text.startswith(("\\o ", "\\? ")):
if self.output_file and not text.startswith(
("\\o ", "\\? ", "\\echo ")
):
try:
with open(self.output_file, "a", encoding="utf-8") as f:
click.echo(text, file=f)
Expand Down Expand Up @@ -800,6 +820,34 @@ def execute_command(self, text, handle_closed_connection=True):
logger.debug("Search path: %r", self.completer.search_path)
return query

def _check_ongoing_transaction_and_allow_quitting(self):
"""Return whether we can really quit, possibly by asking the
user to confirm so if there is an ongoing transaction.
"""
if not self.pgexecute.valid_transaction():
return True
while 1:
try:
choice = click.prompt(
"A transaction is ongoing. Choose `c` to COMMIT, `r` to ROLLBACK, `a` to abort exit.",
default="a",
)
except click.Abort:
# Print newline if user aborts with `^C`, otherwise
# pgcli's prompt will be printed on the same line
# (just after the confirmation prompt).
click.echo(None, err=False)
choice = "a"
choice = choice.lower()
if choice == "a":
return False # do not quit
if choice == "c":
query = self.execute_command("commit")
return query.successful # quit only if query is successful
if choice == "r":
query = self.execute_command("rollback")
return query.successful # quit only if query is successful

def run_cli(self):
logger = self.logger

Expand All @@ -822,6 +870,10 @@ def run_cli(self):
text = self.prompt_app.prompt()
except KeyboardInterrupt:
continue
except EOFError:
if not self._check_ongoing_transaction_and_allow_quitting():
continue
raise

try:
text = self.handle_editor_command(text)
Expand All @@ -831,7 +883,12 @@ def run_cli(self):
click.secho(str(e), err=True, fg="red")
continue

self.handle_watch_command(text)
try:
self.handle_watch_command(text)
except PgCliQuitError:
if not self._check_ongoing_transaction_and_allow_quitting():
continue
raise

self.now = dt.datetime.today()

Expand Down
8 changes: 8 additions & 0 deletions pgcli/pgclirc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ auto_retry_closed_connection = True
# If set to True, table suggestions will include a table alias
generate_aliases = False

# Path to a json file that specifies specific table aliases to use when generate_aliases is set to True
# the format for this file should be:
# {
# "some_table_name": "desired_alias",
# "some_other_table_name": "another_alias"
# }
alias_map_file =

# log_file location.
# In Unix/Linux: ~/.config/pgcli/log
# In Windows: %USERPROFILE%\AppData\Local\dbcli\pgcli\log
Expand Down
28 changes: 27 additions & 1 deletion pgcli/pgcompleter.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import logging
import re
from itertools import count, repeat, chain
Expand Down Expand Up @@ -61,18 +62,38 @@ def Candidate(
normalize_ref = lambda ref: ref if ref[0] == '"' else '"' + ref.lower() + '"'


def generate_alias(tbl):
def generate_alias(tbl, alias_map=None):
"""Generate a table alias, consisting of all upper-case letters in
the table name, or, if there are no upper-case letters, the first letter +
all letters preceded by _
param tbl - unescaped name of the table to alias
"""
if alias_map and tbl in alias_map:
return alias_map[tbl]
return "".join(
[l for l in tbl if l.isupper()]
or [l for l, prev in zip(tbl, "_" + tbl) if prev == "_" and l != "_"]
)


class InvalidMapFile(ValueError):
pass


def load_alias_map_file(path):
try:
with open(path) as fo:
alias_map = json.load(fo)
except FileNotFoundError as err:
raise InvalidMapFile(
f"Cannot read alias_map_file - {err.filename} does not exist"
)
except json.JSONDecodeError:
raise InvalidMapFile(f"Cannot read alias_map_file - {path} is not valid json")
else:
return alias_map


class PGCompleter(Completer):
# keywords_tree: A dict mapping keywords to well known following keywords.
# e.g. 'CREATE': ['TABLE', 'USER', ...],
Expand Down Expand Up @@ -100,6 +121,11 @@ def __init__(self, smart_completion=True, pgspecial=None, settings=None):
self.call_arg_oneliner_max = settings.get("call_arg_oneliner_max", 2)
self.search_path_filter = settings.get("search_path_filter")
self.generate_aliases = settings.get("generate_aliases")
alias_map_file = settings.get("alias_map_file")
if alias_map_file is not None:
self.alias_map = load_alias_map_file(alias_map_file)
else:
self.alias_map = None
self.casing_file = settings.get("casing_file")
self.insert_col_skip_patterns = [
re.compile(pattern)
Expand Down
8 changes: 2 additions & 6 deletions pgcli/pgtoolbar.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
from pkg_resources import packaging

import prompt_toolkit
from prompt_toolkit.key_binding.vi_state import InputMode
from prompt_toolkit.application import get_app

parse_version = packaging.version.parse

vi_modes = {
InputMode.INSERT: "I",
InputMode.NAVIGATION: "N",
InputMode.REPLACE: "R",
InputMode.INSERT_MULTIPLE: "M",
}
if parse_version(prompt_toolkit.__version__) >= parse_version("3.0.6"):
# REPLACE_SINGLE is available in prompt_toolkit >= 3.0.6
if "REPLACE_SINGLE" in {e.name for e in InputMode}:
vi_modes[InputMode.REPLACE_SINGLE] = "R"


Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.black]
line-length = 88
target-version = ['py36']
target-version = ['py38']
include = '\.pyi?$'
exclude = '''
/(
Expand All @@ -19,4 +19,3 @@ exclude = '''
| tests/data
)/
'''

4 changes: 2 additions & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pytest>=2.7.0
tox>=1.9.2
behave>=1.2.4
black>=22.3.0
black>=23.3.0
pexpect==3.3; platform_system != "Windows"
pre-commit>=1.16.0
coverage>=5.0.4
Expand All @@ -10,4 +10,4 @@ docutils>=0.13.1
autopep8>=1.3.3
twine>=1.11.0
wheel>=0.33.6
sshtunnel>=0.4.0
sshtunnel>=0.4.0
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"psycopg >= 3.0.14",
"sqlparse >=0.3.0,<0.5",
"configobj >= 5.0.6",
"pendulum>=2.1.0",
"pendulum>=3.0.0b1",
"cli_helpers[styles] >= 2.2.1",
]

Expand Down Expand Up @@ -51,7 +51,7 @@
"keyring": ["keyring >= 12.2.0"],
"sshtunnel": ["sshtunnel >= 0.4.0"],
},
python_requires=">=3.7",
python_requires=">=3.8",
entry_points="""
[console_scripts]
pgcli=pgcli.main:cli
Expand All @@ -62,11 +62,11 @@
"Operating System :: Unix",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: SQL",
"Topic :: Database",
"Topic :: Database :: Front-Ends",
Expand Down
Loading

0 comments on commit a292c14

Please sign in to comment.