Merging upstream version 4.2.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
7e05478097
commit
853c1070f9
14 changed files with 197 additions and 99 deletions
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
|
@ -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
1
.gitignore
vendored
|
@ -42,6 +42,7 @@ htmlcov/
|
|||
nosetests.xml
|
||||
coverage.xml
|
||||
.pytest_cache
|
||||
tests/behave.ini
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
|
4
AUTHORS
4
AUTHORS
|
@ -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:
|
||||
--------
|
||||
|
|
|
@ -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:
|
||||
-------
|
||||
|
|
|
@ -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)
|
||||
==================
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "4.1.0"
|
||||
__version__ = "4.2.0"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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']
|
||||
|
|
71
setup.py
71
setup.py
|
@ -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",
|
||||
],
|
||||
)
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
[
|
||||
|
|
2
tox.ini
2
tox.ini
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue