1
0
Fork 0

Merging upstream version 4.2.0.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-03-09 08:29:34 +01:00
parent 7e05478097
commit 853c1070f9
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
14 changed files with 197 additions and 99 deletions

View file

@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
services:
postgres:
@ -77,12 +77,13 @@ jobs:
- name: Run unit tests
run: coverage run --source pgcli -m pytest
- name: Run integration tests
env:
PGUSER: postgres
PGPASSWORD: postgres
# - name: Run integration tests
# env:
# PGUSER: postgres
# PGPASSWORD: postgres
# TERM: xterm
run: behave tests/features --no-capture
# run: behave tests/features --no-capture
- name: Check changelog for ReST compliance
run: docutils --halt=warning changelog.rst >/dev/null

1
.gitignore vendored
View file

@ -42,6 +42,7 @@ htmlcov/
nosetests.xml
coverage.xml
.pytest_cache
tests/behave.ini
# Translations
*.mo

View file

@ -134,6 +134,10 @@ Contributors:
* Antonio Aguilar (crazybolillo)
* Andrew M. MacFie (amacfie)
* saucoide
* Chris Rose (offbyone/offby1)
* Mathieu Dupuy (deronnax)
* Chris Novakovic
* Josh Lynch (josh-lynch)
Creator:
--------

View file

@ -179,7 +179,7 @@ Alternatively, you can install ``pgcli`` as a python package using a package
manager called called ``pip``. You will need postgres installed on your system
for this to work.
In depth getting started guide for ``pip`` - https://pip.pypa.io/en/latest/installing.html.
In depth getting started guide for ``pip`` - https://pip.pypa.io/en/latest/installation/
::
@ -209,7 +209,7 @@ If pip is not installed check if easy_install is available on the system.
Linux:
======
In depth getting started guide for ``pip`` - https://pip.pypa.io/en/latest/installing.html.
In depth getting started guide for ``pip`` - https://pip.pypa.io/en/latest/installation/
Check if pip is already available in your system.
@ -352,7 +352,10 @@ choice:
In [3]: my_result = _
Pgcli dropped support for Python<3.8 as of 4.0.0. If you need it, install ``pgcli <= 4.0.0``.
Pgcli dropped support for:
* Python<3.8 as of 4.0.0.
* Python<3.9 as of 4.2.0.
Thanks:
-------

View file

@ -1,3 +1,21 @@
4.2.0 (2025-03-06)
==================
Features
--------
* Add a `--ping` command line option; allows pgcli to replace `pg_isready`
* Changed the packaging metadata from setup.py to pyproject.toml
Bug fixes:
----------
* Avoid raising `NameError` when exiting unsuccessfully in some cases
* Use configured `alias_map_file` to generate table aliases if available.
Internal:
---------
* Drop support for Python 3.8 and add 3.13.
4.1.0 (2024-03-09)
==================

View file

@ -1 +1 @@
__version__ = "4.1.0"
__version__ = "4.2.0"

View file

@ -610,7 +610,7 @@ class PGCli:
click.secho(
f"service '{service}' was not found in {file}", err=True, fg="red"
)
exit(1)
sys.exit(1)
self.connect(
database=service_config.get("dbname"),
host=service_config.get("host"),
@ -710,7 +710,7 @@ class PGCli:
self.logger.handlers = logger_handlers
self.logger.error("traceback: %r", traceback.format_exc())
click.secho(str(e), err=True, fg="red")
exit(1)
sys.exit(1)
self.logger.handlers = logger_handlers
atexit.register(self.ssh_tunnel.stop)
@ -763,7 +763,7 @@ class PGCli:
self.logger.debug("Database connection failed: %r.", e)
self.logger.error("traceback: %r", traceback.format_exc())
click.secho(str(e), err=True, fg="red")
exit(1)
sys.exit(1)
self.pgexecute = pgexecute
@ -1463,6 +1463,13 @@ class PGCli:
is_flag=True,
help="list available databases, then exit.",
)
@click.option(
"--ping",
"ping_database",
is_flag=True,
default=False,
help="Check database connectivity, then exit.",
)
@click.option(
"--auto-vertical-output",
is_flag=True,
@ -1504,6 +1511,7 @@ def cli(
prompt,
prompt_dsn,
list_databases,
ping_database,
auto_vertical_output,
list_dsn,
warn,
@ -1543,7 +1551,7 @@ def cli(
err=True,
fg="red",
)
exit(1)
sys.exit(1)
if ssh_tunnel and not SSH_TUNNEL_SUPPORT:
click.secho(
@ -1552,7 +1560,7 @@ def cli(
err=True,
fg="red",
)
exit(1)
sys.exit(1)
pgcli = PGCli(
prompt_passwd,
@ -1581,8 +1589,8 @@ def cli(
service = database[8:]
elif os.getenv("PGSERVICE") is not None:
service = os.getenv("PGSERVICE")
# because option --list or -l are not supposed to have a db name
if list_databases:
# because option --ping, --list or -l are not supposed to have a db name
if list_databases or ping_database:
database = "postgres"
if dsn != "":
@ -1596,7 +1604,7 @@ def cli(
err=True,
fg="red",
)
exit(1)
sys.exit(1)
except Exception:
click.secho(
"Invalid DSNs found in the config file. "
@ -1604,7 +1612,7 @@ def cli(
err=True,
fg="red",
)
exit(1)
sys.exit(1)
pgcli.connect_uri(dsn_config)
pgcli.dsn_alias = dsn
elif "://" in database:
@ -1626,6 +1634,20 @@ def cli(
sys.exit(0)
if ping_database:
try:
list(pgcli.pgexecute.run("SELECT 1"))
except Exception:
click.secho(
"Could not connect to the database. Please check that the database is running.",
err=True,
fg="red",
)
sys.exit(1)
else:
click.echo("PONG")
sys.exit(0)
pgcli.logger.debug(
"Launch Params: \n" "\tdatabase: %r" "\tuser: %r" "\thost: %r" "\tport: %r",
database,

View file

@ -63,10 +63,17 @@ normalize_ref = lambda ref: ref if ref[0] == '"' else '"' + ref.lower() + '"'
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
"""Generate a table alias.
Given a table name will return an alias for that table using the first of
the following options there's a match for.
1. The predefined alias for table defined in the alias_map.
2. All upper-case letters in the table name.
3. The first letter of the table name and all letters preceded by _
:param tbl: unescaped name of the table to alias
:param alias_map: optional mapping of predefined table aliases
"""
if alias_map and tbl in alias_map:
return alias_map[tbl]
@ -528,7 +535,7 @@ class PGCompleter(Completer):
scoped_cols = self.populate_scoped_cols(tables, suggestion.local_tables)
def make_cand(name, ref):
synonyms = (name, generate_alias(self.case(name)))
synonyms = (name, generate_alias(self.case(name), alias_map=self.alias_map))
return Candidate(qualify(name, ref), 0, "column", synonyms)
def flat_cols():
@ -601,7 +608,7 @@ class PGCompleter(Completer):
tbl = self.case(tbl)
tbls = {normalize_ref(t.ref) for t in tbls}
if self.generate_aliases:
tbl = generate_alias(self.unescape_name(tbl))
tbl = generate_alias(self.unescape_name(tbl), alias_map=self.alias_map)
if normalize_ref(tbl) not in tbls:
return tbl
elif tbl[0] == '"':
@ -644,7 +651,7 @@ class PGCompleter(Completer):
join = "{0} ON {0}.{1} = {2}.{3}".format(
c(left.tbl), c(left.col), rtbl.ref, c(right.col)
)
alias = generate_alias(self.case(left.tbl))
alias = generate_alias(self.case(left.tbl), alias_map=self.alias_map)
synonyms = [
join,
"{0} ON {0}.{1} = {2}.{3}".format(
@ -845,7 +852,7 @@ class PGCompleter(Completer):
cased_tbl = self.case(tbl.name)
if do_alias:
alias = self.alias(cased_tbl, suggestion.table_refs)
synonyms = (cased_tbl, generate_alias(cased_tbl))
synonyms = (cased_tbl, generate_alias(cased_tbl, alias_map=self.alias_map))
maybe_alias = (" " + alias) if do_alias else ""
maybe_schema = (self.case(tbl.schema) + ".") if tbl.schema else ""
suffix = self._arg_list_cache[arg_mode][tbl.meta] if arg_mode else ""

View file

@ -1,3 +1,75 @@
[project]
name = "pgcli"
authors = [{ name = "Pgcli Core Team", email = "pgcli-dev@googlegroups.com" }]
license = { text = "BSD" }
description = "CLI for Postgres Database. With auto-completion and syntax highlighting."
readme = "README.rst"
classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: Unix",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: SQL",
"Topic :: Database",
"Topic :: Database :: Front-Ends",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries :: Python Modules",
]
urls = { Homepage = "https://pgcli.com" }
requires-python = ">=3.9"
dependencies = [
"pgspecial>=2.0.0",
"click >= 4.1",
"Pygments>=2.0", # Pygments has to be Capitalcased. WTF?
# 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; sys_platform != 'win32'",
"psycopg-binary >= 3.0.14; sys_platform == 'win32'",
"sqlparse >=0.3.0,<0.6",
"configobj >= 5.0.6",
"cli_helpers[styles] >= 2.2.1",
# setproctitle is used to mask the password when running `ps` in command line.
# But this is not necessary in Windows since the password is never shown in the
# task manager. Also setproctitle is a hard dependency to install in Windows,
# so we'll only install it if we're not in Windows.
"setproctitle >= 1.1.9; sys_platform != 'win32' and 'CYGWIN' not in sys_platform",
]
dynamic = ["version"]
[project.scripts]
pgcli = "pgcli.main:cli"
[project.optional-dependencies]
keyring = ["keyring >= 12.2.0"]
sshtunnel = ["sshtunnel >= 0.4.0"]
[build-system]
requires = ["setuptools>=61.2"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
include-package-data = false
[tool.setuptools.dynamic]
version = { attr = "pgcli.__version__" }
[tool.setuptools.packages]
find = { namespaces = false }
[tool.setuptools.package-data]
pgcli = [
"pgclirc",
"packages/pgliterals/pgliterals.json",
]
[tool.black]
line-length = 88
target-version = ['py38']

View file

@ -1,71 +0,0 @@
import platform
from setuptools import setup, find_packages
from pgcli import __version__
description = "CLI for Postgres Database. With auto-completion and syntax highlighting."
install_requirements = [
"pgspecial>=2.0.0",
"click >= 4.1",
"Pygments>=2.0", # Pygments has to be Capitalcased. WTF?
# 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; sys_platform != 'win32'",
"psycopg-binary >= 3.0.14; sys_platform == 'win32'",
"sqlparse >=0.3.0,<0.6",
"configobj >= 5.0.6",
"cli_helpers[styles] >= 2.2.1",
]
# setproctitle is used to mask the password when running `ps` in command line.
# But this is not necessary in Windows since the password is never shown in the
# task manager. Also setproctitle is a hard dependency to install in Windows,
# so we'll only install it if we're not in Windows.
if platform.system() != "Windows" and not platform.system().startswith("CYGWIN"):
install_requirements.append("setproctitle >= 1.1.9")
setup(
name="pgcli",
author="Pgcli Core Team",
author_email="pgcli-dev@googlegroups.com",
version=__version__,
license="BSD",
url="http://pgcli.com",
packages=find_packages(),
package_data={"pgcli": ["pgclirc", "packages/pgliterals/pgliterals.json"]},
description=description,
long_description=open("README.rst").read(),
install_requires=install_requirements,
dependency_links=[
"http://github.com/psycopg/repo/tarball/master#egg=psycopg-3.0.10"
],
extras_require={
"keyring": ["keyring >= 12.2.0"],
"sshtunnel": ["sshtunnel >= 0.4.0"],
},
python_requires=">=3.8",
entry_points="""
[console_scripts]
pgcli=pgcli.main:cli
""",
classifiers=[
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: Unix",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"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",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries :: Python Modules",
],
)

View file

@ -51,6 +51,10 @@ Feature: run the cli,
When we list databases
then we see list of databases
Scenario: ping databases
When we ping the database
then we get a pong response
Scenario: run the cli with --username
When we launch dbcli using --username
and we send "\?" command

View file

@ -26,6 +26,19 @@ def step_see_list_databases(context):
context.cmd_output = None
@when("we ping the database")
def step_ping_database(context):
cmd = ["pgcli", "--ping"]
context.cmd_output = subprocess.check_output(cmd, cwd=context.package_root)
@then("we get a pong response")
def step_get_pong_response(context):
# exit code 0 is implied by the presence of cmd_output here, which
# is only set on a successful run.
assert context.cmd_output.strip() == b"PONG", f"Output was {context.cmd_output}"
@when("we run dbcli")
def step_run_cli(context):
wrappers.run_cli(context)

View file

@ -1,5 +1,7 @@
import json
import pytest
from pgcli import pgcompleter
import tempfile
def test_load_alias_map_file_missing_file():
@ -47,12 +49,34 @@ def test_generate_alias_uses_first_char_and_every_preceded_by_underscore(
"table_name, alias_map, alias",
[
("some_table", {"some_table": "my_alias"}, "my_alias"),
pytest.param(
"some_other_table", {"some_table": "my_alias"}, "sot", id="no_match_in_map"
),
],
)
def test_generate_alias_can_use_alias_map(table_name, alias_map, alias):
assert pgcompleter.generate_alias(table_name, alias_map) == alias
@pytest.mark.parametrize(
"table_name, alias_map, alias",
[
("some_table", {"some_table": "my_alias"}, "my_alias"),
],
)
def test_pgcompleter_alias_uses_configured_alias_map(table_name, alias_map, alias):
with tempfile.NamedTemporaryFile(mode="w", suffix=".json") as alias_map_file:
alias_map_file.write(json.dumps(alias_map))
alias_map_file.seek(0)
completer = pgcompleter.PGCompleter(
settings={
"generate_aliases": True,
"alias_map_file": alias_map_file.name,
}
)
assert completer.alias(table_name, []) == alias
@pytest.mark.parametrize(
"table_name, alias_map, alias",
[

View file

@ -1,5 +1,5 @@
[tox]
envlist = py38, py39, py310, py311, py312
envlist = py39, py310, py311, py312, py313
[testenv]
deps = pytest>=2.7.0,<=3.0.7
mock>=1.0.1