summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml6
-rw-r--r--AUTHORS4
-rw-r--r--changelog.rst25
-rw-r--r--debian/TODO7
-rw-r--r--debian/changelog258
-rw-r--r--debian/control36
-rw-r--r--debian/copyright39
-rw-r--r--debian/manpages/pgcli.162
-rw-r--r--debian/pgcli.manpages1
-rwxr-xr-xdebian/rules10
-rw-r--r--debian/source/format1
-rw-r--r--debian/watch3
-rw-r--r--pgcli/__init__.py2
-rw-r--r--pgcli/main.py224
-rw-r--r--pgcli/pgclirc14
-rw-r--r--pgcli/pgexecute.py17
-rw-r--r--setup.py11
-rw-r--r--tests/conftest.py2
-rw-r--r--tests/features/steps/crud_database.py1
-rw-r--r--tests/test_application_name.py17
-rw-r--r--tests/test_main.py99
-rw-r--r--tests/test_pgexecute.py66
-rw-r--r--tests/test_ssh_tunnel.py4
23 files changed, 873 insertions, 36 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 68a69ac..007178f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -67,10 +67,6 @@ jobs:
psql -h localhost -U postgres -p 6432 pgbouncer -c 'show help'
- - name: Install beta version of pendulum
- run: pip install pendulum==3.0.0b1
- if: matrix.python-version == '3.12'
-
- name: Install requirements
run: |
pip install -U pip setuptools
@@ -89,7 +85,7 @@ jobs:
run: behave tests/features --no-capture
- name: Check changelog for ReST compliance
- run: rst2html.py --halt=warning changelog.rst >/dev/null
+ run: docutils --halt=warning changelog.rst >/dev/null
- name: Run Black
run: black --check .
diff --git a/AUTHORS b/AUTHORS
index 5eff7db..9f33ff5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -130,6 +130,10 @@ Contributors:
* blag
* Rob Berry (rob-b)
* Sharon Yogev (sharonyogev)
+ * Hollis Wu (holi0317)
+ * Antonio Aguilar (crazybolillo)
+ * Andrew M. MacFie (amacfie)
+ * saucoide
Creator:
--------
diff --git a/changelog.rst b/changelog.rst
index 7d08839..744e903 100644
--- a/changelog.rst
+++ b/changelog.rst
@@ -1,5 +1,26 @@
+4.1.0 (2024-03-09)
==================
-4.0.1 (2023-11-30)
+
+Features:
+---------
+* Support `PGAPPNAME` as an environment variable and `--application-name` as a command line argument.
+* Add `verbose_errors` config and `\v` special command which enable the
+ displaying of all Postgres error fields received.
+* Show Postgres notifications.
+* Support sqlparse 0.5.x
+* Add `--log-file [filename]` cli argument and `\log-file [filename]` special commands to
+ log to an external file in addition to the normal output
+
+Bug fixes:
+----------
+
+* Fix display of "short host" in prompt (with `\h`) for IPv4 addresses ([issue 964](https://github.com/dbcli/pgcli/issues/964)).
+* Fix backwards display of NOTICEs from a Function ([issue 1443](https://github.com/dbcli/pgcli/issues/1443))
+* Fix psycopg errors when installing on Windows. ([issue 1413](https://https://github.com/dbcli/pgcli/issues/1413))
+* Use a home-made function to display query duration instead of relying on a third-party library (the general behaviour does not change), which fixes the installation of `pgcli` on 32-bit architectures ([issue 1451](https://github.com/dbcli/pgcli/issues/1451))
+
+==================
+4.0.1 (2023-10-30)
==================
Internal:
@@ -7,7 +28,7 @@ Internal:
* Allow stable version of pendulum.
==================
-4.0.0 (2023-11-27)
+4.0.0 (2023-10-27)
==================
Features:
diff --git a/debian/TODO b/debian/TODO
new file mode 100644
index 0000000..0ff43b8
--- /dev/null
+++ b/debian/TODO
@@ -0,0 +1,7 @@
+TODO
+====
+
+ * check/verify/extend options in manpage for current upstream version.
+ * upstream manpage.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Mon, 08 Feb 2021 12:55:51 +0100
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..4050dd8
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,258 @@
+pgcli (4.1.0-1) sid; urgency=medium
+
+ * Uploading to sid.
+ * Merging upstream version 4.1.0.
+ * Updating copyright for 2024.
+ * Updating to standards-version 4.7.0.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Sat, 18 May 2024 07:30:04 +0200
+
+pgcli (4.0.1-1) sid; urgency=medium
+
+ * Uploading to sid.
+ * Merging upstream version 4.0.1.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Wed, 01 Nov 2023 05:39:50 +0100
+
+pgcli (3.5.0-5) sid; urgency=medium
+
+ * Uploading to sid.
+ * Building with fixed psycopg3 (Closes: #1029182).
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Mon, 23 Jan 2023 09:26:07 +0100
+
+pgcli (3.5.0-4) sid; urgency=medium
+
+ * Uploading to sid.
+ * Updating to standards version 4.6.2.
+ * Updating psycopg3 depends (Closes: #1029182).
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Thu, 19 Jan 2023 07:17:12 +0100
+
+pgcli (3.5.0-3) sid; urgency=medium
+
+ * Uploading to sid.
+ * Depending on updated python3-pgspecial (Closes: #1023453).
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Wed, 16 Nov 2022 05:57:41 +0100
+
+pgcli (3.5.0-2) sid; urgency=medium
+
+ * Uploading to sid.
+ * Updating psycopg depends (Closes: #1021367).
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Mon, 10 Oct 2022 17:36:23 +0200
+
+pgcli (3.5.0-1) sid; urgency=medium
+
+ * Uploading to sid.
+ * Merging upstream version 3.5.0.
+ * Updating to standards version 4.6.1.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Wed, 21 Sep 2022 03:44:59 +0200
+
+pgcli (3.4.1-1) sid; urgency=medium
+
+ * Uploading to sid.
+ * Merging upstream version 3.4.1.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Sat, 26 Mar 2022 06:37:10 +0100
+
+pgcli (3.4.0-1) sid; urgency=medium
+
+ * Uploading to sid.
+ * Merging upstream version 3.4.0.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Wed, 02 Mar 2022 13:22:08 +0100
+
+pgcli (3.3.1-1) sid; urgency=medium
+
+ * Uploading to sid.
+ * Merging upstream version 3.3.1.
+ * Updating upstream copyright for 2022.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Sat, 22 Jan 2022 07:37:25 +0100
+
+pgcli (3.3.0-1) sid; urgency=medium
+
+ * Uploading to sid.
+ * Merging upstream version 3.3.0.
+ * Updating packaging copyright for 2022.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Fri, 14 Jan 2022 15:48:00 +0100
+
+pgcli (3.2.0-1) sid; urgency=medium
+
+ * Uploading to sid.
+ * Merging upstream version 3.2.0.
+ * Updating python debhelper sequence.
+ * Updating watch file.
+ * Updating to standards version 4.6.0.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Mon, 06 Sep 2021 06:17:57 +0200
+
+pgcli (3.1.0-3) sid; urgency=medium
+
+ * Uploading to sid.
+ * Removing testsuite field, no tests yet in the package.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Sat, 27 Feb 2021 17:31:19 +0100
+
+pgcli (3.1.0-2) sid; urgency=medium
+
+ * Uploading to sid.
+ * Manually sorting fields in control.
+ * Adding rules-requires-root field.
+ * Removing trailing slash in URL in copyright.
+ * Repeating section per binary package in control for consistency reasons.
+ * Correcting postgres spelling in package long-description.
+ * Correcting postgres spelling in manpage description.
+ * Adding TODO file.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Mon, 08 Feb 2021 13:16:26 +0100
+
+pgcli (3.1.0-1) sid; urgency=medium
+
+ * Uploading to sid.
+ * Adding myself as maintainer, thanks Lennart.
+ * Updating vcs fields.
+ * Updating to debhelper version 13.
+ * Updating to standards version 4.5.1.
+ * Updating homepage field.
+ * Dropping versions from depends fulfilled by buster.
+ * Updating package descriptions.
+ * Updating and harmonizing copyright file.
+ * Removing gbp.conf.
+ * Updating watch file.
+ * Removing vendorizing patch for cli-helpers and depending on it directly, now
+ that it's in debian.
+ * Removing patch to change dependency version for python3-pgspecial
+ (backporting it to buster needs a few packages anyway, so one more doesn't
+ make much of a difference, but we'll save the upstream diff here).
+ * Switching rules to use unchanged pybuild since we're using cli-helpers now.
+ * Correcting typo in timeformat in changelog.
+ * Moving manpage to subdirectory within debian sources.
+ * Reformating, harmonizing and updating manpage.
+ * Wrap-and-sorting debian directory.
+ * Removing some automatically detected python depends.
+ * Adding python3-sqlparse to build-depends rather than python3-pgspecial, needed to run the test target.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Mon, 08 Feb 2021 12:21:23 +0100
+
+pgcli (3.1.0-0.3) unstable; urgency=medium
+
+ * Non-maintainer upload.
+ * Installing bash-completion (Closes: #902121).
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Sun, 07 Feb 2021 09:05:38 +0100
+
+pgcli (3.1.0-0.2) unstable; urgency=medium
+
+ * Non-maintainer upload.
+ * Uploading pgcli to unstable now that pendulum passed NEW:
+ - pgcli will still not work due to missing pytzdata (NEW; depends of pendulum),
+ but it will as soon pytzdata is accepted.
+ - the current pgcli in unstable doesn't work anyway because of python3-harmonze,
+ so we're not worse off by uploading now.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Sun, 07 Feb 2021 07:36:00 +0100
+
+pgcli (3.1.0-0.1) experimental; urgency=medium
+
+ * Non-maintainer upload.
+ * New upstream release, using pendulum (Closes: #972598).
+ * Refreshing 0001-Change-dependencies-and-rename-entrypoint.patch.
+
+ -- Daniel Baumann <daniel.baumann@progress-linux.org> Sat, 30 Jan 2021 09:59:01 +0100
+
+pgcli (3.0.0-1) unstable; urgency=medium
+
+ * New upstream release (Closes: #959973)
+
+ -- Lennart Weller <lhw@ring0.de> Fri, 08 May 2020 10:29:31 +0200
+
+pgcli (2.2.0-4) unstable; urgency=medium
+
+ * Add dependencies on tabulate and terminaltables (Closes: #950621)
+
+ -- Lennart Weller <lhw@ring0.de> Wed, 12 Feb 2020 16:11:13 +0100
+
+pgcli (2.2.0-3) unstable; urgency=medium
+
+ * pkg-resources hard dependency for minimal systems (Closes: #944455)
+
+ -- Lennart Weller <lhw@ring0.de> Tue, 14 Jan 2020 11:22:17 +0100
+
+pgcli (2.2.0-2) unstable; urgency=medium
+
+ * New upstream release (Closes: #917963)
+ * New debian Standards-Version 4.4.1
+
+ -- Lennart Weller <lhw@ring0.de> Sun, 12 Jan 2020 22:31:03 +0100
+
+pgcli (1.9.1-1) unstable; urgency=medium
+
+ * New upstream release (Closes: #894321)
+ * Vendorized version of cli_helpers library
+
+ -- Lennart Weller <lhw@ring0.de> Wed, 25 Apr 2018 14:36:42 +0200
+
+pgcli (1.6.0-1) unstable; urgency=medium
+
+ * New upstream release
+ * Fixes issues with psycopg2 (Closes: #865630)
+
+ -- Lennart Weller <lhw@ring0.de> Fri, 30 Jun 2017 11:41:22 +0200
+
+pgcli (1.4.0-1) unstable; urgency=medium
+
+ * New upstream release
+
+ -- Lennart Weller <lhw@ring0.de> Mon, 06 Mar 2017 15:14:26 +0100
+
+pgcli (1.3.1-2) unstable; urgency=medium
+
+ * Added pkg-ressources as dependency
+
+ -- Lennart Weller <lhw@ring0.de> Wed, 11 Jan 2017 12:42:09 +0100
+
+pgcli (1.3.1-1) unstable; urgency=medium
+
+ * New upstrem release (Closes: #844394)
+
+ -- Lennart Weller <lhw@ring0.de> Tue, 15 Nov 2016 14:54:59 +0100
+
+pgcli (1.1.0-1) unstable; urgency=low
+
+ * New upstream release
+
+ -- Lennart Weller <lhw@ring0.de> Wed, 27 Jul 2016 14:43:23 +0200
+
+pgcli (1.0.0-1) unstable; urgency=medium
+
+ * Imported new upstream release (Closes: #829021)
+ * Added python3-humanize dependency
+ * Updated debian Standards-Version
+ * Added manpage
+ * Updated Vcs links
+
+ -- Lennart Weller <lhw@ring0.de> Fri, 01 Jul 2016 10:53:56 +0200
+
+pgcli (0.20.1-3) unstable; urgency=medium
+
+ * Merged upstream patch to support prompt_toolkit == 0.57 (Closes: #811478)
+ * Updated Build-Depends for pgspecial
+
+ -- Lennart Weller <lhw@ring0.de> Tue, 19 Jan 2016 13:44:14 +0100
+
+pgcli (0.20.1-2) unstable; urgency=medium
+
+ * Added patch to get pgcli to work with prompt_toolkit >= 0.51
+
+ -- Lennart Weller <lhw@ring0.de> Fri, 27 Nov 2015 14:15:20 +0100
+
+pgcli (0.20.1-1) unstable; urgency=low
+
+ * Initial release (Closes: #794250)
+
+ -- Lennart Weller <lhw@ring0.de> Fri, 31 Jul 2015 18:16:30 +0200
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..a680c99
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,36 @@
+Source: pgcli
+Section: database
+Priority: optional
+Maintainer: Daniel Baumann <daniel.baumann@progress-linux.org>
+Uploaders:
+ Lennart Weller <lhw@ring0.de>,
+Build-Depends:
+ debhelper-compat (= 13),
+ dh-sequence-python3,
+ python3,
+ python3-setuptools,
+ python3-sqlparse (>= 0.3),
+ python3-psycopg (>= 3.1.7-4~),
+Rules-Requires-Root: no
+Standards-Version: 4.7.0
+Homepage: https://www.pgcli.com
+Vcs-Browser: https://git.progress-linux.org/users/daniel.baumann/debian/packages/pgcli
+Vcs-Git: https://git.progress-linux.org/users/daniel.baumann/debian/packages/pgcli
+
+Package: pgcli
+Section: database
+Architecture: all
+Depends:
+ python3-cli-helpers,
+ python3-pendulum,
+ python3-pgspecial (>= 2),
+ python3-pkg-resources,
+ python3-prompt-toolkit (>= 3.0),
+ python3-sqlparse (>= 0.3),
+ python3-tabulate,
+ python3-terminaltables,
+ ${misc:Depends},
+ ${python3:Depends},
+Description: CLI for PostgreSQL with auto-completion and syntax highlighting
+ pgcli is a command line interface for PostgreSQL with auto-completion and
+ syntax highlighting. It is also capable of pretty printing tabular data.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..7c77472
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,39 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: pgcli
+Upstream-Contact: Amjith Ramanujam <amjith.r@gmail.com>
+Source: https://www.pgcli.com
+
+Files: *
+Copyright: 2014-2024 Amjith Ramanujam <amjith.r@gmail.com>
+License: BSD-3-clause
+
+Files: debian/*
+Copyright: 2021-2024 Daniel Baumann <daniel.baumann@progress-linux.org>
+ 2015-2020 Lennart Weller <lhw@ring0.de>
+License: BSD-3-clause
+
+License: BSD-3-clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ .
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ .
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ .
+ * Neither the name of dbcli nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/debian/manpages/pgcli.1 b/debian/manpages/pgcli.1
new file mode 100644
index 0000000..0628377
--- /dev/null
+++ b/debian/manpages/pgcli.1
@@ -0,0 +1,62 @@
+.TH "PGCLI" "1" "3.1.0" "pgcli" "DBCLI"
+
+.SH NAME
+pgcli \- CLI for PostgreSQL with auto-completion and syntax highlighting
+
+.SH SYNOPSIS
+\fBpgcli\fR [\fIOPTIONS\fR] [\fIDATABASE\fR] [\fIUSERNAME\fR]
+
+.SH DESCRIPTION
+pgcli is a command line interface for PostgreSQL with auto-completion and
+syntax highlighting. It is also capable of pretty printing tabular data.
+
+.SH OPTIONS
+
+.IP "\-h|\-\-host \fITEXT\fR" 4
+Host address of the postgres database.
+
+.IP "\-p|\-\-port \fIINTEGER\fR" 4
+Port number at which the postgres instance is listening.
+
+.IP "\-U|\-\-user \fITEXT\fR" 4
+User name to connect to the postgres database.
+
+.IP "\-W|\-\-password" 4
+Force password prompt.
+
+.IP "\-w|\-\-no-password" 4
+Never prompt for password.
+
+.IP "\-v|\-\-version" 4
+Version of pgcli.
+
+.IP "\-d|\-\-dbname \fITEXT\fR" 4
+Database name to connect to.
+
+.IP "\-\-pgclirc \fITEXT\fR" 4
+Location of pgclirc file.
+
+.IP "\-\-help" 4
+Show this message and exit.
+
+.SH EXAMPLES
+$ pgcli local_database
+.TP
+$ pgcli postgres://amjith:passw0rd@example.com:5432/app_db
+.TP
+$ pgcli -h localhost -p 5432 -U amjith app_db
+
+.SH SEE ALSO
+litecli(1)
+.TP
+mycli(1)
+.TP
+iredis(1)
+
+.SH HOMEPAGE
+More information about pgcli and the DBCLI project can be found on the homepage at https://www.pgcli.com and https://www.dbcli.com.
+
+.SH AUTHORS
+pgcli was written by Amjith Ramanujam <amjith.r@gmail.com>.
+.TP
+This manual page was written by Lennart Weller <lhw@ring0.de>, for the Debian project (but may be used by others).
diff --git a/debian/pgcli.manpages b/debian/pgcli.manpages
new file mode 100644
index 0000000..2fb19fb
--- /dev/null
+++ b/debian/pgcli.manpages
@@ -0,0 +1 @@
+debian/manpages/*
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..f837a53
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,10 @@
+#!/usr/bin/make -f
+
+export PYBUILD_NAME=pgcli
+
+%:
+ dh ${@} --buildsystem=pybuild
+
+execute_after_dh_auto_install:
+ mkdir -p debian/pgcli/usr/share/bash-completion/completions
+ cp pgcli-completion.bash debian/pgcli/usr/share/bash-completion/completions/pgcli
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/debian/watch b/debian/watch
new file mode 100644
index 0000000..e20100f
--- /dev/null
+++ b/debian/watch
@@ -0,0 +1,3 @@
+version=4
+opts=filenamemangle=s/.+\/v?(\d\S+)\.tar\.gz/pgcli-$1\.tar\.gz/ \
+https://github.com/dbcli/pgcli/tags .*/v?(\d\S+)\.tar\.gz
diff --git a/pgcli/__init__.py b/pgcli/__init__.py
index 76ad18b..7039708 100644
--- a/pgcli/__init__.py
+++ b/pgcli/__init__.py
@@ -1 +1 @@
-__version__ = "4.0.1"
+__version__ = "4.1.0"
diff --git a/pgcli/main.py b/pgcli/main.py
index f95c800..056a940 100644
--- a/pgcli/main.py
+++ b/pgcli/main.py
@@ -11,9 +11,9 @@ import logging
import threading
import shutil
import functools
-import pendulum
import datetime as dt
import itertools
+import pathlib
import platform
from time import time, sleep
from typing import Optional
@@ -74,8 +74,9 @@ from urllib.parse import urlparse
from getpass import getuser
-from psycopg import OperationalError, InterfaceError
+from psycopg import OperationalError, InterfaceError, Notify
from psycopg.conninfo import make_conninfo, conninfo_to_dict
+from psycopg.errors import Diagnostic
from collections import namedtuple
@@ -129,6 +130,15 @@ class PgCliQuitError(Exception):
pass
+def notify_callback(notify: Notify):
+ click.secho(
+ 'Notification received on channel "{}" (PID {}):\n{}'.format(
+ notify.channel, notify.pid, notify.payload
+ ),
+ fg="green",
+ )
+
+
class PGCli:
default_prompt = "\\u@\\h:\\d> "
max_len_prompt = 30
@@ -165,6 +175,7 @@ class PGCli:
pgexecute=None,
pgclirc_file=None,
row_limit=None,
+ application_name="pgcli",
single_connection=False,
less_chatty=None,
prompt=None,
@@ -172,6 +183,7 @@ class PGCli:
auto_vertical_output=False,
warn=None,
ssh_tunnel_url: Optional[str] = None,
+ log_file: Optional[str] = None,
):
self.force_passwd_prompt = force_passwd_prompt
self.never_passwd_prompt = never_passwd_prompt
@@ -210,6 +222,8 @@ class PGCli:
else:
self.row_limit = c["main"].as_int("row_limit")
+ self.application_name = application_name
+
# if not specified, set to DEFAULT_MAX_FIELD_WIDTH
# if specified but empty, set to None to disable truncation
# ellipsis will take at least 3 symbols, so this can't be less than 3 if specified and > 0
@@ -237,6 +251,9 @@ class PGCli:
)
self.less_chatty = bool(less_chatty) or c["main"].as_bool("less_chatty")
+ self.verbose_errors = "verbose_errors" in c["main"] and c["main"].as_bool(
+ "verbose_errors"
+ )
self.null_string = c["main"].get("null_string", "<null>")
self.prompt_format = (
prompt
@@ -295,6 +312,11 @@ class PGCli:
self.ssh_tunnel_url = ssh_tunnel_url
self.ssh_tunnel = None
+ if log_file:
+ with open(log_file, "a+"):
+ pass # ensure writeable
+ self.log_file = log_file
+
# formatter setup
self.formatter = TabularOutputFormatter(format_name=c["main"]["table_format"])
register_new_formatter(self.formatter)
@@ -355,6 +377,12 @@ class PGCli:
"Send all query results to file.",
)
self.pgspecial.register(
+ self.write_to_logfile,
+ "\\log-file",
+ "\\log-file [filename]",
+ "Log all query results to a logfile, in addition to the normal output destination.",
+ )
+ self.pgspecial.register(
self.info_connection, "\\conninfo", "\\conninfo", "Get connection details"
)
self.pgspecial.register(
@@ -378,6 +406,26 @@ class PGCli:
"Echo a string to the query output channel.",
)
+ self.pgspecial.register(
+ self.toggle_verbose_errors,
+ "\\v",
+ "\\v [on|off]",
+ "Toggle verbose errors.",
+ )
+
+ def toggle_verbose_errors(self, pattern, **_):
+ flag = pattern.strip()
+
+ if flag == "on":
+ self.verbose_errors = True
+ elif flag == "off":
+ self.verbose_errors = False
+ else:
+ self.verbose_errors = not self.verbose_errors
+
+ message = "Verbose errors " + "on." if self.verbose_errors else "off."
+ return [(None, None, None, message)]
+
def echo(self, pattern, **_):
return [(None, None, None, pattern)]
@@ -473,6 +521,26 @@ class PGCli:
explain_mode=self.explain_mode,
)
+ def write_to_logfile(self, pattern, **_):
+ if not pattern:
+ self.log_file = None
+ message = "Logfile capture disabled"
+ return [(None, None, None, message, "", True, True)]
+
+ log_file = pathlib.Path(pattern).expanduser().absolute()
+
+ try:
+ with open(log_file, "a+"):
+ pass # ensure writeable
+ except OSError as e:
+ self.log_file = None
+ message = str(e) + "\nLogfile capture disabled"
+ return [(None, None, None, message, "", False, True)]
+
+ self.log_file = str(log_file)
+ message = 'Writing to file "%s"' % self.log_file
+ return [(None, None, None, message, "", True, True)]
+
def write_to_file(self, pattern, **_):
if not pattern:
self.output_file = None
@@ -568,7 +636,7 @@ class PGCli:
if not database:
database = user
- kwargs.setdefault("application_name", "pgcli")
+ kwargs.setdefault("application_name", self.application_name)
# If password prompt is not forced but no password is provided, try
# getting it from environment variable.
@@ -658,7 +726,16 @@ class PGCli:
# prompt for a password (no -w flag), prompt for a passwd and try again.
try:
try:
- pgexecute = PGExecute(database, user, passwd, host, port, dsn, **kwargs)
+ pgexecute = PGExecute(
+ database,
+ user,
+ passwd,
+ host,
+ port,
+ dsn,
+ notify_callback,
+ **kwargs,
+ )
except (OperationalError, InterfaceError) as e:
if should_ask_for_password(e):
passwd = click.prompt(
@@ -668,7 +745,14 @@ class PGCli:
type=str,
)
pgexecute = PGExecute(
- database, user, passwd, host, port, dsn, **kwargs
+ database,
+ user,
+ passwd,
+ host,
+ port,
+ dsn,
+ notify_callback,
+ **kwargs,
)
else:
raise e
@@ -775,7 +859,7 @@ class PGCli:
else:
try:
if self.output_file and not text.startswith(
- ("\\o ", "\\? ", "\\echo ")
+ ("\\o ", "\\log-file", "\\? ", "\\echo ")
):
try:
with open(self.output_file, "a", encoding="utf-8") as f:
@@ -787,6 +871,23 @@ class PGCli:
else:
if output:
self.echo_via_pager("\n".join(output))
+
+ # Log to file in addition to normal output
+ if (
+ self.log_file
+ and not text.startswith(("\\o ", "\\log-file", "\\? ", "\\echo "))
+ and not text.strip() == ""
+ ):
+ try:
+ with open(self.log_file, "a", encoding="utf-8") as f:
+ click.echo(
+ dt.datetime.now().isoformat(), file=f
+ ) # timestamp log
+ click.echo(text, file=f)
+ click.echo("\n".join(output), file=f)
+ click.echo("", file=f) # extra newline
+ except OSError as e:
+ click.secho(str(e), err=True, fg="red")
except KeyboardInterrupt:
pass
@@ -797,9 +898,9 @@ class PGCli:
"Time: %0.03fs (%s), executed in: %0.03fs (%s)"
% (
query.total_time,
- pendulum.Duration(seconds=query.total_time).in_words(),
+ duration_in_words(query.total_time),
query.execution_time,
- pendulum.Duration(seconds=query.execution_time).in_words(),
+ duration_in_words(query.execution_time),
)
)
else:
@@ -1053,7 +1154,7 @@ class PGCli:
res = self.pgexecute.run(
text,
self.pgspecial,
- exception_formatter,
+ lambda x: exception_formatter(x, self.verbose_errors),
on_error_resume,
explain_mode=self.explain_mode,
)
@@ -1338,6 +1439,12 @@ class PGCli:
help="Set threshold for row limit prompt. Use 0 to disable prompt.",
)
@click.option(
+ "--application-name",
+ default="pgcli",
+ envvar="PGAPPNAME",
+ help="Application name for the connection.",
+)
+@click.option(
"--less-chatty",
"less_chatty",
is_flag=True,
@@ -1371,6 +1478,11 @@ class PGCli:
default=None,
help="Open an SSH tunnel to the given address and connect to the database from it.",
)
+@click.option(
+ "--log-file",
+ default=None,
+ help="Write all queries & output into a file, in addition to the normal output destination.",
+)
@click.argument("dbname", default=lambda: None, envvar="PGDATABASE", nargs=1)
@click.argument("username", default=lambda: None, envvar="PGUSER", nargs=1)
def cli(
@@ -1387,6 +1499,7 @@ def cli(
pgclirc,
dsn,
row_limit,
+ application_name,
less_chatty,
prompt,
prompt_dsn,
@@ -1395,6 +1508,7 @@ def cli(
list_dsn,
warn,
ssh_tunnel: str,
+ log_file: str,
):
if version:
print("Version:", __version__)
@@ -1445,6 +1559,7 @@ def cli(
never_prompt,
pgclirc_file=pgclirc,
row_limit=row_limit,
+ application_name=application_name,
single_connection=single_connection,
less_chatty=less_chatty,
prompt=prompt,
@@ -1452,6 +1567,7 @@ def cli(
auto_vertical_output=auto_vertical_output,
warn=warn,
ssh_tunnel_url=ssh_tunnel,
+ log_file=log_file,
)
# Choose which ever one has a valid value.
@@ -1583,8 +1699,71 @@ def is_select(status):
return status.split(None, 1)[0].lower() == "select"
-def exception_formatter(e):
- return click.style(str(e), fg="red")
+def diagnostic_output(diagnostic: Diagnostic) -> str:
+ fields = []
+
+ if diagnostic.severity is not None:
+ fields.append("Severity: " + diagnostic.severity)
+
+ if diagnostic.severity_nonlocalized is not None:
+ fields.append("Severity (non-localized): " + diagnostic.severity_nonlocalized)
+
+ if diagnostic.sqlstate is not None:
+ fields.append("SQLSTATE code: " + diagnostic.sqlstate)
+
+ if diagnostic.message_primary is not None:
+ fields.append("Message: " + diagnostic.message_primary)
+
+ if diagnostic.message_detail is not None:
+ fields.append("Detail: " + diagnostic.message_detail)
+
+ if diagnostic.message_hint is not None:
+ fields.append("Hint: " + diagnostic.message_hint)
+
+ if diagnostic.statement_position is not None:
+ fields.append("Position: " + diagnostic.statement_position)
+
+ if diagnostic.internal_position is not None:
+ fields.append("Internal position: " + diagnostic.internal_position)
+
+ if diagnostic.internal_query is not None:
+ fields.append("Internal query: " + diagnostic.internal_query)
+
+ if diagnostic.context is not None:
+ fields.append("Where: " + diagnostic.context)
+
+ if diagnostic.schema_name is not None:
+ fields.append("Schema name: " + diagnostic.schema_name)
+
+ if diagnostic.table_name is not None:
+ fields.append("Table name: " + diagnostic.table_name)
+
+ if diagnostic.column_name is not None:
+ fields.append("Column name: " + diagnostic.column_name)
+
+ if diagnostic.datatype_name is not None:
+ fields.append("Data type name: " + diagnostic.datatype_name)
+
+ if diagnostic.constraint_name is not None:
+ fields.append("Constraint name: " + diagnostic.constraint_name)
+
+ if diagnostic.source_file is not None:
+ fields.append("File: " + diagnostic.source_file)
+
+ if diagnostic.source_line is not None:
+ fields.append("Line: " + diagnostic.source_line)
+
+ if diagnostic.source_function is not None:
+ fields.append("Routine: " + diagnostic.source_function)
+
+ return "\n".join(fields)
+
+
+def exception_formatter(e, verbose_errors: bool = False):
+ s = str(e)
+ if verbose_errors:
+ s += "\n" + diagnostic_output(e.diag)
+ return click.style(s, fg="red")
def format_output(title, cur, headers, status, settings, explain_mode=False):
@@ -1724,5 +1903,28 @@ def parse_service_info(service):
return service_conf, service_file
+def duration_in_words(duration_in_seconds: float) -> str:
+ if not duration_in_seconds:
+ return "0 seconds"
+ components = []
+ hours, remainder = divmod(duration_in_seconds, 3600)
+ if hours > 1:
+ components.append(f"{hours} hours")
+ elif hours == 1:
+ components.append("1 hour")
+ minutes, seconds = divmod(remainder, 60)
+ if minutes > 1:
+ components.append(f"{minutes} minutes")
+ elif minutes == 1:
+ components.append("1 minute")
+ if seconds >= 2:
+ components.append(f"{int(seconds)} seconds")
+ elif seconds >= 1:
+ components.append("1 second")
+ elif seconds:
+ components.append(f"{round(seconds, 3)} second")
+ return " ".join(components)
+
+
if __name__ == "__main__":
cli()
diff --git a/pgcli/pgclirc b/pgcli/pgclirc
index 51f7eae..dd8b15f 100644
--- a/pgcli/pgclirc
+++ b/pgcli/pgclirc
@@ -33,10 +33,11 @@ multi_line_mode = psql
# "unconditional_update" will warn you of update statements that don't have a where clause
destructive_warning = drop, shutdown, delete, truncate, alter, update, unconditional_update
-# Destructive warning can restart the connection if this is enabled and the
-# user declines. This means that any current uncommitted transaction can be
-# aborted if the user doesn't want to proceed with a destructive_warning
-# statement.
+# When `destructive_warning` is on and the user declines to proceed with a
+# destructive statement, the current transaction (if any) is left untouched,
+# by default. When setting `destructive_warning_restarts_connection` to
+# "True", the connection to the server is restarted. In that case, the
+# transaction (if any) is rolled back.
destructive_warning_restarts_connection = False
# When this option is on (and if `destructive_warning` is not empty),
@@ -155,6 +156,11 @@ max_field_width = 500
# Skip intro on startup and goodbye on exit
less_chatty = False
+# Show all Postgres error fields (as listed in
+# https://www.postgresql.org/docs/current/protocol-error-fields.html).
+# Can be toggled with \v.
+verbose_errors = False
+
# Postgres prompt
# \t - Current date and time
# \u - Username
diff --git a/pgcli/pgexecute.py b/pgcli/pgexecute.py
index 497d681..e091757 100644
--- a/pgcli/pgexecute.py
+++ b/pgcli/pgexecute.py
@@ -1,3 +1,4 @@
+import ipaddress
import logging
import traceback
from collections import namedtuple
@@ -166,6 +167,7 @@ class PGExecute:
host=None,
port=None,
dsn=None,
+ notify_callback=None,
**kwargs,
):
self._conn_params = {}
@@ -178,6 +180,7 @@ class PGExecute:
self.port = None
self.server_version = None
self.extra_args = None
+ self.notify_callback = notify_callback
self.connect(database, user, password, host, port, dsn, **kwargs)
self.reset_expanded = None
@@ -236,6 +239,9 @@ class PGExecute:
self.conn = conn
self.conn.autocommit = True
+ if self.notify_callback is not None:
+ self.conn.add_notify_handler(self.notify_callback)
+
# When we connect using a DSN, we don't really know what db,
# user, etc. we connected to. Let's read it.
# Note: moved this after setting autocommit because of #664.
@@ -273,6 +279,11 @@ class PGExecute:
@property
def short_host(self):
+ try:
+ ipaddress.ip_address(self.host)
+ return self.host
+ except ValueError:
+ pass
if "," in self.host:
host, _, _ = self.host.partition(",")
else:
@@ -431,7 +442,11 @@ class PGExecute:
def handle_notices(n):
nonlocal title
- title = f"{n.message_primary}\n{n.message_detail}\n{title}"
+ title = f"{title}"
+ if n.message_primary is not None:
+ title = f"{title}\n{n.message_primary}"
+ if n.message_detail is not None:
+ title = f"{title}\n{n.message_detail}"
self.conn.add_notice_handler(handle_notices)
diff --git a/setup.py b/setup.py
index 9a398a4..f4606c2 100644
--- a/setup.py
+++ b/setup.py
@@ -12,10 +12,10 @@ install_requirements = [
# We still need to use pt-2 unless pt-3 released on Fedora32
# see: https://github.com/dbcli/pgcli/pull/1197
"prompt_toolkit>=2.0.6,<4.0.0",
- "psycopg >= 3.0.14",
- "sqlparse >=0.3.0,<0.5",
+ "psycopg >= 3.0.14; sys_platform != 'win32'",
+ "psycopg-binary >= 3.0.14; sys_platform == 'win32'",
+ "sqlparse >=0.3.0,<0.6",
"configobj >= 5.0.6",
- "pendulum>=2.1.0",
"cli_helpers[styles] >= 2.2.1",
]
@@ -27,11 +27,6 @@ install_requirements = [
if platform.system() != "Windows" and not platform.system().startswith("CYGWIN"):
install_requirements.append("setproctitle >= 1.1.9")
-# Windows will require the binary psycopg to run pgcli
-if platform.system() == "Windows":
- install_requirements.append("psycopg-binary >= 3.0.14")
-
-
setup(
name="pgcli",
author="Pgcli Core Team",
diff --git a/tests/conftest.py b/tests/conftest.py
index 33cddf2..e50f1fe 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -9,6 +9,7 @@ from utils import (
db_connection,
drop_tables,
)
+import pgcli.main
import pgcli.pgexecute
@@ -37,6 +38,7 @@ def executor(connection):
password=POSTGRES_PASSWORD,
port=POSTGRES_PORT,
dsn=None,
+ notify_callback=pgcli.main.notify_callback,
)
diff --git a/tests/features/steps/crud_database.py b/tests/features/steps/crud_database.py
index 87cdc85..9507d46 100644
--- a/tests/features/steps/crud_database.py
+++ b/tests/features/steps/crud_database.py
@@ -3,6 +3,7 @@ Steps for behavioral style tests are defined in this module.
Each step is defined by the string decorating it.
This string is used to call the step in "*.feature" file.
"""
+
import pexpect
from behave import when, then
diff --git a/tests/test_application_name.py b/tests/test_application_name.py
new file mode 100644
index 0000000..5fac5b2
--- /dev/null
+++ b/tests/test_application_name.py
@@ -0,0 +1,17 @@
+from unittest.mock import patch
+
+from click.testing import CliRunner
+
+from pgcli.main import cli
+from pgcli.pgexecute import PGExecute
+
+
+def test_application_name_in_env():
+ runner = CliRunner()
+ app_name = "wonderful_app"
+ with patch.object(PGExecute, "__init__") as mock_pgxecute:
+ runner.invoke(
+ cli, ["127.0.0.1:5432/hello", "user"], env={"PGAPPNAME": app_name}
+ )
+ kwargs = mock_pgxecute.call_args.kwargs
+ assert kwargs.get("application_name") == app_name
diff --git a/tests/test_main.py b/tests/test_main.py
index cbf20a6..3683d49 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -1,5 +1,8 @@
import os
import platform
+import re
+import tempfile
+import datetime
from unittest import mock
import pytest
@@ -11,7 +14,9 @@ except ImportError:
from pgcli.main import (
obfuscate_process_password,
+ duration_in_words,
format_output,
+ notify_callback,
PGCli,
OutputSettings,
COLOR_CODE_REGEX,
@@ -297,6 +302,24 @@ def test_i_works(tmpdir, executor):
@dbtest
+def test_toggle_verbose_errors(executor):
+ cli = PGCli(pgexecute=executor)
+
+ cli._evaluate_command("\\v on")
+ assert cli.verbose_errors
+ output, _ = cli._evaluate_command("SELECT 1/0")
+ assert "SQLSTATE" in output[0]
+
+ cli._evaluate_command("\\v off")
+ assert not cli.verbose_errors
+ output, _ = cli._evaluate_command("SELECT 1/0")
+ assert "SQLSTATE" not in output[0]
+
+ cli._evaluate_command("\\v")
+ assert cli.verbose_errors
+
+
+@dbtest
def test_echo_works(executor):
cli = PGCli(pgexecute=executor)
statement = r"\echo asdf"
@@ -313,6 +336,34 @@ def test_qecho_works(executor):
@dbtest
+def test_logfile_works(executor):
+ with tempfile.TemporaryDirectory() as tmpdir:
+ log_file = f"{tmpdir}/tempfile.log"
+ cli = PGCli(pgexecute=executor, log_file=log_file)
+ statement = r"\qecho hello!"
+ cli.execute_command(statement)
+ with open(log_file, "r") as f:
+ log_contents = f.readlines()
+ assert datetime.datetime.fromisoformat(log_contents[0].strip())
+ assert log_contents[1].strip() == r"\qecho hello!"
+ assert log_contents[2].strip() == "hello!"
+
+
+@dbtest
+def test_logfile_unwriteable_file(executor):
+ cli = PGCli(pgexecute=executor)
+ statement = r"\log-file forbidden.log"
+ with mock.patch("builtins.open") as mock_open:
+ mock_open.side_effect = PermissionError(
+ "[Errno 13] Permission denied: 'forbidden.log'"
+ )
+ result = run(executor, statement, pgspecial=cli.pgspecial)
+ assert result == [
+ "[Errno 13] Permission denied: 'forbidden.log'\nLogfile capture disabled"
+ ]
+
+
+@dbtest
def test_watch_works(executor):
cli = PGCli(pgexecute=executor)
@@ -431,6 +482,7 @@ def test_pg_service_file(tmpdir):
"b_host",
"5435",
"",
+ notify_callback,
application_name="pgcli",
)
del os.environ["PGPASSWORD"]
@@ -486,5 +538,50 @@ def test_application_name_db_uri(tmpdir):
cli = PGCli(pgclirc_file=str(tmpdir.join("rcfile")))
cli.connect_uri("postgres://bar@baz.com/?application_name=cow")
mock_pgexecute.assert_called_with(
- "bar", "bar", "", "baz.com", "", "", application_name="cow"
+ "bar", "bar", "", "baz.com", "", "", notify_callback, application_name="cow"
)
+
+
+@pytest.mark.parametrize(
+ "duration_in_seconds,words",
+ [
+ (0, "0 seconds"),
+ (0.0009, "0.001 second"),
+ (0.0005, "0.001 second"),
+ (0.0004, "0.0 second"), # not perfect, but will do
+ (0.2, "0.2 second"),
+ (1, "1 second"),
+ (1.4, "1 second"),
+ (2, "2 seconds"),
+ (3.4, "3 seconds"),
+ (60, "1 minute"),
+ (61, "1 minute 1 second"),
+ (123, "2 minutes 3 seconds"),
+ (3600, "1 hour"),
+ (7235, "2 hours 35 seconds"),
+ (9005, "2 hours 30 minutes 5 seconds"),
+ (86401, "24 hours 1 second"),
+ ],
+)
+def test_duration_in_words(duration_in_seconds, words):
+ assert duration_in_words(duration_in_seconds) == words
+
+
+@dbtest
+def test_notifications(executor):
+ run(executor, "listen chan1")
+
+ with mock.patch("pgcli.main.click.secho") as mock_secho:
+ run(executor, "notify chan1, 'testing1'")
+ mock_secho.assert_called()
+ arg = mock_secho.call_args_list[0].args[0]
+ assert re.match(
+ r'Notification received on channel "chan1" \(PID \d+\):\ntesting1',
+ arg,
+ )
+
+ run(executor, "unlisten chan1")
+
+ with mock.patch("pgcli.main.click.secho") as mock_secho:
+ run(executor, "notify chan1, 'testing2'")
+ mock_secho.assert_not_called()
diff --git a/tests/test_pgexecute.py b/tests/test_pgexecute.py
index 636795b..f1cadfd 100644
--- a/tests/test_pgexecute.py
+++ b/tests/test_pgexecute.py
@@ -1,3 +1,4 @@
+import re
from textwrap import dedent
import psycopg
@@ -6,7 +7,7 @@ from unittest.mock import patch, MagicMock
from pgspecial.main import PGSpecial, NO_QUERY
from utils import run, dbtest, requires_json, requires_jsonb
-from pgcli.main import PGCli
+from pgcli.main import PGCli, exception_formatter as main_exception_formatter
from pgcli.packages.parseutils.meta import FunctionMetadata
@@ -219,8 +220,33 @@ def test_database_list(executor):
@dbtest
def test_invalid_syntax(executor, exception_formatter):
- result = run(executor, "invalid syntax!", exception_formatter=exception_formatter)
+ result = run(
+ executor,
+ "invalid syntax!",
+ exception_formatter=lambda x: main_exception_formatter(x, verbose_errors=False),
+ )
assert 'syntax error at or near "invalid"' in result[0]
+ assert "SQLSTATE" not in result[0]
+
+
+@dbtest
+def test_invalid_syntax_verbose(executor):
+ result = run(
+ executor,
+ "invalid syntax!",
+ exception_formatter=lambda x: main_exception_formatter(x, verbose_errors=True),
+ )
+ fields = r"""
+Severity: ERROR
+Severity \(non-localized\): ERROR
+SQLSTATE code: 42601
+Message: syntax error at or near "invalid"
+Position: 1
+File: scan\.l
+Line: \d+
+Routine: scanner_yyerror
+ """.strip()
+ assert re.search(fields, result[0])
@dbtest
@@ -691,6 +717,38 @@ def test_function_definition(executor):
@dbtest
+def test_function_notice_order(executor):
+ run(
+ executor,
+ """
+ CREATE OR REPLACE FUNCTION demo_order() RETURNS VOID AS
+ $$
+ BEGIN
+ RAISE NOTICE 'first';
+ RAISE NOTICE 'second';
+ RAISE NOTICE 'third';
+ RAISE NOTICE 'fourth';
+ RAISE NOTICE 'fifth';
+ RAISE NOTICE 'sixth';
+ END;
+ $$
+ LANGUAGE plpgsql;
+ """,
+ )
+
+ executor.function_definition("demo_order")
+
+ result = run(executor, "select demo_order()")
+ assert "first\nsecond\nthird\nfourth\nfifth\nsixth" in result[0]
+ assert "+------------+" in result[1]
+ assert "| demo_order |" in result[2]
+ assert "|------------|" in result[3]
+ assert "| |" in result[4]
+ assert "+------------+" in result[5]
+ assert "SELECT 1" in result[6]
+
+
+@dbtest
def test_view_definition(executor):
run(executor, "create table tbl1 (a text, b numeric)")
run(executor, "create view vw1 AS SELECT * FROM tbl1")
@@ -721,6 +779,10 @@ def test_short_host(executor):
executor, "host", "localhost1.example.org,localhost2.example.org"
):
assert executor.short_host == "localhost1"
+ with patch.object(executor, "host", "ec2-11-222-333-444.compute-1.amazonaws.com"):
+ assert executor.short_host == "ec2-11-222-333-444"
+ with patch.object(executor, "host", "1.2.3.4"):
+ assert executor.short_host == "1.2.3.4"
class VirtualCursor:
diff --git a/tests/test_ssh_tunnel.py b/tests/test_ssh_tunnel.py
index ae865f4..983212b 100644
--- a/tests/test_ssh_tunnel.py
+++ b/tests/test_ssh_tunnel.py
@@ -6,7 +6,7 @@ from configobj import ConfigObj
from click.testing import CliRunner
from sshtunnel import SSHTunnelForwarder
-from pgcli.main import cli, PGCli
+from pgcli.main import cli, notify_callback, PGCli
from pgcli.pgexecute import PGExecute
@@ -61,6 +61,7 @@ def test_ssh_tunnel(
"127.0.0.1",
pgcli.ssh_tunnel.local_bind_ports[0],
"",
+ notify_callback,
)
mock_ssh_tunnel_forwarder.reset_mock()
mock_pgexecute.reset_mock()
@@ -96,6 +97,7 @@ def test_ssh_tunnel(
"127.0.0.1",
pgcli.ssh_tunnel.local_bind_ports[0],
"",
+ notify_callback,
)
mock_ssh_tunnel_forwarder.reset_mock()
mock_pgexecute.reset_mock()