pgcli 4.5.0__tar.gz → 4.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. {pgcli-4.5.0 → pgcli-4.6.0}/AUTHORS +6 -0
  2. {pgcli-4.5.0 → pgcli-4.6.0}/PKG-INFO +3 -3
  3. {pgcli-4.5.0 → pgcli-4.6.0}/changelog.rst +30 -0
  4. pgcli-4.6.0/pgcli/__init__.py +1 -0
  5. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/main.py +52 -5
  6. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/sqlcompletion.py +25 -2
  7. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/pgcompleter.py +7 -0
  8. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/pgexecute.py +2 -1
  9. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli.egg-info/PKG-INFO +3 -3
  10. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli.egg-info/SOURCES.txt +2 -0
  11. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli.egg-info/requires.txt +1 -1
  12. pgcli-4.6.0/pgcli.egg-info/scm_file_list.json +122 -0
  13. pgcli-4.6.0/pgcli.egg-info/scm_version.json +8 -0
  14. {pgcli-4.5.0 → pgcli-4.6.0}/pyproject.toml +2 -5
  15. pgcli-4.6.0/release.py +228 -0
  16. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_main.py +65 -0
  17. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_pgcompleter.py +16 -0
  18. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_sqlcompletion.py +26 -0
  19. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_ssh_tunnel.py +52 -22
  20. pgcli-4.5.0/pgcli/__init__.py +0 -1
  21. pgcli-4.5.0/release.py +0 -132
  22. {pgcli-4.5.0 → pgcli-4.6.0}/.coveragerc +0 -0
  23. {pgcli-4.5.0 → pgcli-4.6.0}/.editorconfig +0 -0
  24. {pgcli-4.5.0 → pgcli-4.6.0}/.git-blame-ignore-revs +0 -0
  25. {pgcli-4.5.0 → pgcli-4.6.0}/.github/ISSUE_TEMPLATE.md +0 -0
  26. {pgcli-4.5.0 → pgcli-4.6.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  27. {pgcli-4.5.0 → pgcli-4.6.0}/.github/workflows/ci.yml +0 -0
  28. {pgcli-4.5.0 → pgcli-4.6.0}/.github/workflows/codeql.yml +0 -0
  29. {pgcli-4.5.0 → pgcli-4.6.0}/.github/workflows/codex-review.yml +0 -0
  30. {pgcli-4.5.0 → pgcli-4.6.0}/.github/workflows/publish.yml +0 -0
  31. {pgcli-4.5.0 → pgcli-4.6.0}/.gitignore +0 -0
  32. {pgcli-4.5.0 → pgcli-4.6.0}/.pre-commit-config.yaml +0 -0
  33. {pgcli-4.5.0 → pgcli-4.6.0}/CONTRIBUTING.rst +0 -0
  34. {pgcli-4.5.0 → pgcli-4.6.0}/Dockerfile +0 -0
  35. {pgcli-4.5.0 → pgcli-4.6.0}/LICENSE.txt +0 -0
  36. {pgcli-4.5.0 → pgcli-4.6.0}/MANIFEST.in +0 -0
  37. {pgcli-4.5.0 → pgcli-4.6.0}/README.rst +0 -0
  38. {pgcli-4.5.0 → pgcli-4.6.0}/RELEASES.md +0 -0
  39. {pgcli-4.5.0 → pgcli-4.6.0}/TODO +0 -0
  40. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/__main__.py +0 -0
  41. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/auth.py +0 -0
  42. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/completion_refresher.py +0 -0
  43. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/config.py +0 -0
  44. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/explain_output_formatter.py +0 -0
  45. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/key_bindings.py +0 -0
  46. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/magic.py +0 -0
  47. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/__init__.py +0 -0
  48. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/formatter/__init__.py +0 -0
  49. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/formatter/sqlformatter.py +0 -0
  50. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/parseutils/__init__.py +0 -0
  51. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/parseutils/ctes.py +0 -0
  52. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/parseutils/meta.py +0 -0
  53. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/parseutils/tables.py +0 -0
  54. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/parseutils/utils.py +0 -0
  55. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/pgliterals/__init__.py +0 -0
  56. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/pgliterals/main.py +0 -0
  57. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/pgliterals/pgliterals.json +0 -0
  58. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/prioritization.py +0 -0
  59. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/packages/prompt_utils.py +0 -0
  60. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/pgbuffer.py +0 -0
  61. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/pgclirc +0 -0
  62. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/pgstyle.py +0 -0
  63. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/pgtoolbar.py +0 -0
  64. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli/pyev.py +0 -0
  65. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli-completion.bash +0 -0
  66. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli.egg-info/dependency_links.txt +0 -0
  67. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli.egg-info/entry_points.txt +0 -0
  68. {pgcli-4.5.0 → pgcli-4.6.0}/pgcli.egg-info/top_level.txt +0 -0
  69. {pgcli-4.5.0 → pgcli-4.6.0}/post-install +0 -0
  70. {pgcli-4.5.0 → pgcli-4.6.0}/post-remove +0 -0
  71. {pgcli-4.5.0 → pgcli-4.6.0}/pylintrc +0 -0
  72. {pgcli-4.5.0 → pgcli-4.6.0}/sanity_checks.txt +0 -0
  73. {pgcli-4.5.0 → pgcli-4.6.0}/screenshots/image01.png +0 -0
  74. {pgcli-4.5.0 → pgcli-4.6.0}/screenshots/image02.png +0 -0
  75. {pgcli-4.5.0 → pgcli-4.6.0}/screenshots/kharkiv-destroyed.jpg +0 -0
  76. {pgcli-4.5.0 → pgcli-4.6.0}/screenshots/pgcli.gif +0 -0
  77. {pgcli-4.5.0 → pgcli-4.6.0}/setup.cfg +0 -0
  78. {pgcli-4.5.0 → pgcli-4.6.0}/tests/conftest.py +0 -0
  79. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/__init__.py +0 -0
  80. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/auto_vertical.feature +0 -0
  81. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/basic_commands.feature +0 -0
  82. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/crud_database.feature +0 -0
  83. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/crud_table.feature +0 -0
  84. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/db_utils.py +0 -0
  85. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/environment.py +0 -0
  86. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/expanded.feature +0 -0
  87. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/fixture_data/help.txt +0 -0
  88. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/fixture_data/help_commands.txt +0 -0
  89. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/fixture_data/mock_pg_service.conf +0 -0
  90. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/fixture_utils.py +0 -0
  91. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/iocommands.feature +0 -0
  92. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/named_queries.feature +0 -0
  93. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/pgbouncer.feature +0 -0
  94. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/specials.feature +0 -0
  95. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/steps/__init__.py +0 -0
  96. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/steps/auto_vertical.py +0 -0
  97. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/steps/basic_commands.py +0 -0
  98. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/steps/crud_database.py +0 -0
  99. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/steps/crud_table.py +0 -0
  100. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/steps/expanded.py +0 -0
  101. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/steps/iocommands.py +0 -0
  102. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/steps/named_queries.py +0 -0
  103. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/steps/pgbouncer.py +0 -0
  104. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/steps/specials.py +0 -0
  105. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/steps/wrappers.py +0 -0
  106. {pgcli-4.5.0 → pgcli-4.6.0}/tests/features/wrappager.py +0 -0
  107. {pgcli-4.5.0 → pgcli-4.6.0}/tests/formatter/__init__.py +0 -0
  108. {pgcli-4.5.0 → pgcli-4.6.0}/tests/formatter/test_sqlformatter.py +0 -0
  109. {pgcli-4.5.0 → pgcli-4.6.0}/tests/metadata.py +0 -0
  110. {pgcli-4.5.0 → pgcli-4.6.0}/tests/parseutils/test_ctes.py +0 -0
  111. {pgcli-4.5.0 → pgcli-4.6.0}/tests/parseutils/test_function_metadata.py +0 -0
  112. {pgcli-4.5.0 → pgcli-4.6.0}/tests/parseutils/test_parseutils.py +0 -0
  113. {pgcli-4.5.0 → pgcli-4.6.0}/tests/pytest.ini +0 -0
  114. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_application_name.py +0 -0
  115. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_auth.py +0 -0
  116. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_completion_refresher.py +0 -0
  117. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_config.py +0 -0
  118. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_fuzzy_completion.py +0 -0
  119. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_init_commands_simple.py +0 -0
  120. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_naive_completion.py +0 -0
  121. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_pgexecute.py +0 -0
  122. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_pgspecial.py +0 -0
  123. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_prioritization.py +0 -0
  124. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_prompt_utils.py +0 -0
  125. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_rowlimit.py +0 -0
  126. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_smart_completion_multiple_schemata.py +0 -0
  127. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_smart_completion_public_schema_only.py +0 -0
  128. {pgcli-4.5.0 → pgcli-4.6.0}/tests/test_trailing_comments.py +0 -0
  129. {pgcli-4.5.0 → pgcli-4.6.0}/tests/utils.py +0 -0
  130. {pgcli-4.5.0 → pgcli-4.6.0}/tox.ini +0 -0
@@ -144,10 +144,16 @@ Contributors:
144
144
  * Jay Knight (jay-knight)
145
145
  * fbdb
146
146
  * Charbel Jacquin (charbeljc)
147
+ * Jeronimo Garcia (bechampion)
147
148
  * Devadathan M B (devadathanmb)
148
149
  * Charalampos Stratakis
149
150
  * Laszlo Bimba (bimlas)
150
151
  * Anjanna
152
+ * Shayan Golshani (shgol)
153
+ * Tommi Kyntölä (kynde)
154
+ * Diego
155
+ * Chris (ChrisJr404)
156
+ * Pieter Ouwerkerk (pouwerkerk)
151
157
 
152
158
  Creator:
153
159
  --------
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgcli
3
- Version: 4.5.0
3
+ Version: 4.6.0
4
4
  Summary: CLI for Postgres Database. With auto-completion and syntax highlighting.
5
5
  Author-email: Pgcli Core Team <pgcli-dev@googlegroups.com>
6
- License: BSD
6
+ License-Expression: BSD-3-Clause
7
7
  Project-URL: Homepage, https://pgcli.com
8
8
  Classifier: Intended Audience :: Developers
9
9
  Classifier: Operating System :: Unix
@@ -29,7 +29,7 @@ Requires-Dist: Pygments>=2.0
29
29
  Requires-Dist: prompt_toolkit<4.0.0,>=2.0.6
30
30
  Requires-Dist: psycopg>=3.0.14; sys_platform != "win32"
31
31
  Requires-Dist: psycopg-binary>=3.0.14; sys_platform == "win32"
32
- Requires-Dist: sqlparse<0.6,>=0.3.0
32
+ Requires-Dist: sqlparse<0.7,>=0.3.0
33
33
  Requires-Dist: configobj>=5.0.6
34
34
  Requires-Dist: cli_helpers[styles]>=2.4.0
35
35
  Requires-Dist: setproctitle>=1.1.9; sys_platform != "win32" and "CYGWIN" not in sys_platform
@@ -1,3 +1,31 @@
1
+ 4.6.0 (2026-08-26)
2
+ ==================
3
+
4
+ Bug fixes:
5
+ ----------
6
+ * Restore cursor shape behaviour for Emacs mode
7
+ * Fix ``TypeError: cannot use a string pattern on a bytes-like object`` when
8
+ completion metadata comes back as bytes (e.g. ``SQL_ASCII`` client encoding).
9
+ * Suggest columns, not datatypes, after a column literally named ``type`` in a ``SELECT`` list.
10
+ * Allow ``sqlparse`` 0.6.x. sqlparse 0.6.0 fixes several denial-of-service
11
+ issues (CVE-2026-59893, CVE-2026-54284, CVE-2026-71491) and a string-escaping
12
+ bug (CVE-2026-59894); the previous ``<0.6`` cap prevented users from
13
+ installing the fixed release.
14
+
15
+ Features:
16
+ ---------
17
+ * Honor the ``PSQL_EDITOR`` environment variable when opening the external
18
+ editor (``\\e``, ``\\ev``, ``\\ef``, ``\\ne``), matching psql's precedence of
19
+ ``PSQL_EDITOR``, then ``EDITOR``, then ``VISUAL`` ([issue 1398](https://github.com/dbcli/pgcli/issues/1398)).
20
+ * Add ``\\ne <name>`` to edit a named query in the external editor. Loads the
21
+ named query's SQL into ``$EDITOR``; on save it is written back to the
22
+ ``[named queries]`` section, creating it if it does not exist. Complements
23
+ ``\\ns`` (save) by making longer queries easier to edit ([issue 1430](https://github.com/dbcli/pgcli/issues/1430)).
24
+ * Enable ``.pgpass`` support for SSH tunnel connections.
25
+ * Preserve original hostname for ``.pgpass`` lookup using PostgreSQL's ``hostaddr`` parameter
26
+ * SSH tunnel endpoint (``127.0.0.1``) is passed via ``hostaddr``, keeping ``host`` for ``.pgpass``
27
+ * Works with both DSN and host/port connection styles
28
+
1
29
  4.5.0 (2026-06-02)
2
30
  ==================
3
31
 
@@ -20,6 +48,8 @@ Bug fixes:
20
48
  * Fix trailing SQL comments preventing query submission and execution.
21
49
  * ``SELECT 1; -- note`` now submits correctly in multiline mode
22
50
  * ``rstrip(";")`` in ``pgexecute.py`` now handles comments after the semicolon
51
+ * Fix completion crash when tables are created during refresh.
52
+ * Suggest columns after `GROUP BY`, like `ORDER BY` already does.
23
53
 
24
54
  4.4.0 (2025-12-24)
25
55
  ==================
@@ -0,0 +1 @@
1
+ __version__ = "4.6.0"
@@ -146,6 +146,17 @@ def notify_callback(notify: Notify):
146
146
  )
147
147
 
148
148
 
149
+ def get_editor():
150
+ """Pick the external editor for ``\\e``/``\\ev``/``\\ef``/``\\ne``.
151
+
152
+ Mirrors psql, which checks ``PSQL_EDITOR`` first, then ``EDITOR``, then
153
+ ``VISUAL``. Returning ``None`` when none are set lets click fall back to
154
+ its platform default, so the behaviour is unchanged for anyone who wasn't
155
+ setting ``PSQL_EDITOR``.
156
+ """
157
+ return os.environ.get("PSQL_EDITOR") or os.environ.get("EDITOR") or os.environ.get("VISUAL") or None
158
+
159
+
149
160
  class PGCli:
150
161
  default_prompt = "\\u@\\h:\\d> "
151
162
  max_len_prompt = 30
@@ -319,6 +330,31 @@ class PGCli:
319
330
  message = f"Named query quiet mode: {status}"
320
331
  return [(None, None, None, message)]
321
332
 
333
+ def edit_named_query(self, pattern, **_):
334
+ r"""Edit (or create) a named query in the external editor (\ne name).
335
+
336
+ Loads the named query's SQL into ``$EDITOR``; on save, persists it back
337
+ to the ``[named queries]`` section. If the name does not exist, the
338
+ editor opens empty and saving creates it.
339
+ """
340
+ name = pattern.strip()
341
+ if not name:
342
+ return [(None, None, None, "Usage: \\ne <name>")]
343
+
344
+ existing = NamedQueries.instance.get(name)
345
+ sql, message = special.open_external_editor(sql=existing or "", editor=get_editor())
346
+ if message:
347
+ return [(None, None, None, message)]
348
+
349
+ sql = (sql or "").strip()
350
+ if not sql:
351
+ return [(None, None, None, f"{name}: empty query, not saved.")]
352
+ if existing is not None and sql == existing.strip():
353
+ return [(None, None, None, f"{name}: no changes.")]
354
+
355
+ NamedQueries.instance.save(name, sql)
356
+ return [(None, None, None, f"{name}: {'Created' if existing is None else 'Saved'}")]
357
+
322
358
  def _is_named_query_execution(self, text):
323
359
  """Check if the command is a named query execution (\n <name>)."""
324
360
  text = text.strip()
@@ -334,6 +370,13 @@ class PGCli:
334
370
  case_sensitive=True,
335
371
  )
336
372
 
373
+ self.pgspecial.register(
374
+ self.edit_named_query,
375
+ "\\ne",
376
+ "\\ne name",
377
+ "Edit a named query in the external editor.",
378
+ )
379
+
337
380
  self.pgspecial.register(
338
381
  self.change_db,
339
382
  "\\c",
@@ -652,7 +695,7 @@ class PGCli:
652
695
  if self.force_passwd_prompt and not passwd:
653
696
  passwd = click.prompt("Password for %s" % user, hide_input=True, show_default=False, type=str)
654
697
 
655
- key = f"{user}@{host}"
698
+ key = f"{user}@{host}@{port}"
656
699
 
657
700
  if not passwd and auth.keyring:
658
701
  passwd = auth.keyring_get_password(key)
@@ -719,11 +762,15 @@ class PGCli:
719
762
  self.logger.handlers = logger_handlers
720
763
 
721
764
  atexit.register(self.ssh_tunnel.stop)
722
- host = "127.0.0.1"
765
+ # Preserve original host for .pgpass lookup and SSL certificate verification.
766
+ # Use hostaddr to specify the actual connection endpoint (SSH tunnel).
767
+ hostaddr = "127.0.0.1"
723
768
  port = self.ssh_tunnel.local_bind_ports[0]
724
769
 
725
770
  if dsn:
726
- dsn = make_conninfo(dsn, host=host, port=port)
771
+ dsn = make_conninfo(dsn, host=host, hostaddr=hostaddr, port=port)
772
+ else:
773
+ kwargs["hostaddr"] = hostaddr
727
774
 
728
775
  # Attempt to connect to the database.
729
776
  # Note that passwd may be empty on the first attempt. If connection
@@ -796,7 +843,7 @@ class PGCli:
796
843
  query = self.pgexecute.view_definition(spec)
797
844
  elif editor_command == "\\ef":
798
845
  query = self.pgexecute.function_definition(spec)
799
- sql, message = special.open_external_editor(filename, sql=query)
846
+ sql, message = special.open_external_editor(filename, sql=query, editor=get_editor())
800
847
  if message:
801
848
  # Something went wrong. Raise an exception and bail.
802
849
  raise RuntimeError(message)
@@ -1099,7 +1146,7 @@ class PGCli:
1099
1146
  enable_suspend=True,
1100
1147
  editing_mode=EditingMode.VI if self.vi_mode else EditingMode.EMACS,
1101
1148
  search_ignore_case=True,
1102
- cursor=ModalCursorShapeConfig(),
1149
+ cursor=ModalCursorShapeConfig() if self.vi_mode else None,
1103
1150
  )
1104
1151
 
1105
1152
  return prompt_app
@@ -278,6 +278,29 @@ def suggest_special(text):
278
278
  return (Keyword(), Special())
279
279
 
280
280
 
281
+ def _is_datatype_keyword(token, stmt):
282
+ """Decide whether a `type` token is the SQL TYPE keyword or a column name.
283
+
284
+ `type` is a non-reserved word in postgres, so sqlparse tags a column
285
+ literally named "type" as a keyword. The TYPE keyword only introduces a
286
+ datatype in DDL contexts (`ALTER COLUMN bar TYPE`, `SET DATA TYPE`,
287
+ `CREATE TYPE`), never directly inside a DML select/insert/update list.
288
+ """
289
+ if isinstance(token, str):
290
+ # An explicit internal request for datatype suggestions, e.g. the
291
+ # parenthesized column list in `CREATE TABLE foo (bar <CURSOR>`.
292
+ return True
293
+
294
+ if not token.is_keyword:
295
+ return False
296
+
297
+ prev_keyword, _ = find_prev_keyword(stmt.text_before_cursor.rstrip(), n_skip=1)
298
+ if prev_keyword is None:
299
+ return False
300
+
301
+ return prev_keyword.ttype is not sqlparse.tokens.Keyword.DML
302
+
303
+
281
304
  def suggest_based_on_last_token(token, stmt):
282
305
  if isinstance(token, str):
283
306
  token_v = token.lower()
@@ -383,7 +406,7 @@ def suggest_based_on_last_token(token, stmt):
383
406
  # E.g. 'UPDATE foo SET'
384
407
  return (Column(table_refs=stmt.get_tables(), local_tables=stmt.local_tables),)
385
408
 
386
- elif token_v in ("select", "where", "having", "order by", "distinct"):
409
+ elif token_v in ("select", "where", "having", "group by", "order by", "distinct"):
387
410
  return _suggest_expression(token_v, stmt)
388
411
  elif token_v == "as":
389
412
  # Don't suggest anything for aliases
@@ -492,7 +515,7 @@ def suggest_based_on_last_token(token, stmt):
492
515
  return suggest_based_on_last_token(prev_keyword, stmt)
493
516
  else:
494
517
  return ()
495
- elif token_v in ("type", "::"):
518
+ elif token_v == "::" or (token_v == "type" and _is_datatype_keyword(token, stmt)):
496
519
  # ALTER TABLE foo SET DATA TYPE bar
497
520
  # SELECT foo::bar
498
521
  # Note that tables are a form of composite type in postgresql, so
@@ -141,6 +141,11 @@ class PGCompleter(Completer):
141
141
  self.all_completions = set(self.keywords + self.functions)
142
142
 
143
143
  def escape_name(self, name):
144
+ if isinstance(name, bytes):
145
+ # Identifiers come back as bytes when the client encoding is one
146
+ # psycopg cannot decode (e.g. SQL_ASCII), see issue #1405.
147
+ name = name.decode("utf-8", "replace")
148
+
144
149
  if name and ((not self.name_pattern.match(name)) or (name.upper() in self.reserved_words) or (name.upper() in self.functions)):
145
150
  name = '"%s"' % name
146
151
 
@@ -229,6 +234,8 @@ class PGCompleter(Completer):
229
234
  has_default=has_default,
230
235
  default=default,
231
236
  )
237
+ metadata.setdefault(schema, {})
238
+ metadata[schema].setdefault(relname, OrderedDict())
232
239
  metadata[schema][relname][colname] = column
233
240
  self.all_completions.add(colname)
234
241
 
@@ -215,7 +215,8 @@ class PGExecute:
215
215
  new_params.update(kwargs)
216
216
 
217
217
  if new_params["dsn"]:
218
- new_params = {"dsn": new_params["dsn"], "password": new_params["password"]}
218
+ # When using DSN, only keep dsn, password, and hostaddr (for SSH tunnels)
219
+ new_params = {k: v for k, v in new_params.items() if k in ("dsn", "password", "hostaddr")}
219
220
 
220
221
  if new_params["password"]:
221
222
  new_params["dsn"] = make_conninfo(new_params["dsn"], password=new_params.pop("password"))
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgcli
3
- Version: 4.5.0
3
+ Version: 4.6.0
4
4
  Summary: CLI for Postgres Database. With auto-completion and syntax highlighting.
5
5
  Author-email: Pgcli Core Team <pgcli-dev@googlegroups.com>
6
- License: BSD
6
+ License-Expression: BSD-3-Clause
7
7
  Project-URL: Homepage, https://pgcli.com
8
8
  Classifier: Intended Audience :: Developers
9
9
  Classifier: Operating System :: Unix
@@ -29,7 +29,7 @@ Requires-Dist: Pygments>=2.0
29
29
  Requires-Dist: prompt_toolkit<4.0.0,>=2.0.6
30
30
  Requires-Dist: psycopg>=3.0.14; sys_platform != "win32"
31
31
  Requires-Dist: psycopg-binary>=3.0.14; sys_platform == "win32"
32
- Requires-Dist: sqlparse<0.6,>=0.3.0
32
+ Requires-Dist: sqlparse<0.7,>=0.3.0
33
33
  Requires-Dist: configobj>=5.0.6
34
34
  Requires-Dist: cli_helpers[styles]>=2.4.0
35
35
  Requires-Dist: setproctitle>=1.1.9; sys_platform != "win32" and "CYGWIN" not in sys_platform
@@ -47,6 +47,8 @@ pgcli.egg-info/SOURCES.txt
47
47
  pgcli.egg-info/dependency_links.txt
48
48
  pgcli.egg-info/entry_points.txt
49
49
  pgcli.egg-info/requires.txt
50
+ pgcli.egg-info/scm_file_list.json
51
+ pgcli.egg-info/scm_version.json
50
52
  pgcli.egg-info/top_level.txt
51
53
  pgcli/packages/__init__.py
52
54
  pgcli/packages/prioritization.py
@@ -2,7 +2,7 @@ pgspecial>=2.0.0
2
2
  click!=8.1.8,!=8.2.*,!=8.3.0,<9,>=4.1
3
3
  Pygments>=2.0
4
4
  prompt_toolkit<4.0.0,>=2.0.6
5
- sqlparse<0.6,>=0.3.0
5
+ sqlparse<0.7,>=0.3.0
6
6
  configobj>=5.0.6
7
7
  cli_helpers[styles]>=2.4.0
8
8
  tzlocal>=5.2
@@ -0,0 +1,122 @@
1
+ {
2
+ "files": [
3
+ ".coveragerc",
4
+ ".editorconfig",
5
+ ".git-blame-ignore-revs",
6
+ ".github/ISSUE_TEMPLATE.md",
7
+ ".github/PULL_REQUEST_TEMPLATE.md",
8
+ ".github/workflows/ci.yml",
9
+ ".github/workflows/codeql.yml",
10
+ ".github/workflows/codex-review.yml",
11
+ ".github/workflows/publish.yml",
12
+ ".gitignore",
13
+ ".pre-commit-config.yaml",
14
+ "AUTHORS",
15
+ "CONTRIBUTING.rst",
16
+ "Dockerfile",
17
+ "LICENSE.txt",
18
+ "MANIFEST.in",
19
+ "README.rst",
20
+ "RELEASES.md",
21
+ "TODO",
22
+ "changelog.rst",
23
+ "pgcli-completion.bash",
24
+ "pgcli/__init__.py",
25
+ "pgcli/__main__.py",
26
+ "pgcli/auth.py",
27
+ "pgcli/completion_refresher.py",
28
+ "pgcli/config.py",
29
+ "pgcli/explain_output_formatter.py",
30
+ "pgcli/key_bindings.py",
31
+ "pgcli/magic.py",
32
+ "pgcli/main.py",
33
+ "pgcli/packages/__init__.py",
34
+ "pgcli/packages/formatter/__init__.py",
35
+ "pgcli/packages/formatter/sqlformatter.py",
36
+ "pgcli/packages/parseutils/__init__.py",
37
+ "pgcli/packages/parseutils/ctes.py",
38
+ "pgcli/packages/parseutils/meta.py",
39
+ "pgcli/packages/parseutils/tables.py",
40
+ "pgcli/packages/parseutils/utils.py",
41
+ "pgcli/packages/pgliterals/__init__.py",
42
+ "pgcli/packages/pgliterals/main.py",
43
+ "pgcli/packages/pgliterals/pgliterals.json",
44
+ "pgcli/packages/prioritization.py",
45
+ "pgcli/packages/prompt_utils.py",
46
+ "pgcli/packages/sqlcompletion.py",
47
+ "pgcli/pgbuffer.py",
48
+ "pgcli/pgclirc",
49
+ "pgcli/pgcompleter.py",
50
+ "pgcli/pgexecute.py",
51
+ "pgcli/pgstyle.py",
52
+ "pgcli/pgtoolbar.py",
53
+ "pgcli/pyev.py",
54
+ "post-install",
55
+ "post-remove",
56
+ "pylintrc",
57
+ "pyproject.toml",
58
+ "release.py",
59
+ "sanity_checks.txt",
60
+ "screenshots/image01.png",
61
+ "screenshots/image02.png",
62
+ "screenshots/kharkiv-destroyed.jpg",
63
+ "screenshots/pgcli.gif",
64
+ "tests/conftest.py",
65
+ "tests/features/__init__.py",
66
+ "tests/features/auto_vertical.feature",
67
+ "tests/features/basic_commands.feature",
68
+ "tests/features/crud_database.feature",
69
+ "tests/features/crud_table.feature",
70
+ "tests/features/db_utils.py",
71
+ "tests/features/environment.py",
72
+ "tests/features/expanded.feature",
73
+ "tests/features/fixture_data/help.txt",
74
+ "tests/features/fixture_data/help_commands.txt",
75
+ "tests/features/fixture_data/mock_pg_service.conf",
76
+ "tests/features/fixture_utils.py",
77
+ "tests/features/iocommands.feature",
78
+ "tests/features/named_queries.feature",
79
+ "tests/features/pgbouncer.feature",
80
+ "tests/features/specials.feature",
81
+ "tests/features/steps/__init__.py",
82
+ "tests/features/steps/auto_vertical.py",
83
+ "tests/features/steps/basic_commands.py",
84
+ "tests/features/steps/crud_database.py",
85
+ "tests/features/steps/crud_table.py",
86
+ "tests/features/steps/expanded.py",
87
+ "tests/features/steps/iocommands.py",
88
+ "tests/features/steps/named_queries.py",
89
+ "tests/features/steps/pgbouncer.py",
90
+ "tests/features/steps/specials.py",
91
+ "tests/features/steps/wrappers.py",
92
+ "tests/features/wrappager.py",
93
+ "tests/formatter/__init__.py",
94
+ "tests/formatter/test_sqlformatter.py",
95
+ "tests/metadata.py",
96
+ "tests/parseutils/test_ctes.py",
97
+ "tests/parseutils/test_function_metadata.py",
98
+ "tests/parseutils/test_parseutils.py",
99
+ "tests/pytest.ini",
100
+ "tests/test_application_name.py",
101
+ "tests/test_auth.py",
102
+ "tests/test_completion_refresher.py",
103
+ "tests/test_config.py",
104
+ "tests/test_fuzzy_completion.py",
105
+ "tests/test_init_commands_simple.py",
106
+ "tests/test_main.py",
107
+ "tests/test_naive_completion.py",
108
+ "tests/test_pgcompleter.py",
109
+ "tests/test_pgexecute.py",
110
+ "tests/test_pgspecial.py",
111
+ "tests/test_prioritization.py",
112
+ "tests/test_prompt_utils.py",
113
+ "tests/test_rowlimit.py",
114
+ "tests/test_smart_completion_multiple_schemata.py",
115
+ "tests/test_smart_completion_public_schema_only.py",
116
+ "tests/test_sqlcompletion.py",
117
+ "tests/test_ssh_tunnel.py",
118
+ "tests/test_trailing_comments.py",
119
+ "tests/utils.py",
120
+ "tox.ini"
121
+ ]
122
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "4.6.0",
3
+ "distance": 0,
4
+ "node": "g787a77a8c8fd15e241b463baa2c66068354a62e0",
5
+ "dirty": false,
6
+ "branch": "main",
7
+ "node_date": "2026-08-26"
8
+ }
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "pgcli"
3
3
  authors = [{ name = "Pgcli Core Team", email = "pgcli-dev@googlegroups.com" }]
4
- license = { text = "BSD" }
4
+ license = "BSD-3-Clause"
5
5
  description = "CLI for Postgres Database. With auto-completion and syntax highlighting."
6
6
  readme = "README.rst"
7
7
  classifiers = [
@@ -32,7 +32,7 @@ dependencies = [
32
32
  "prompt_toolkit>=2.0.6,<4.0.0",
33
33
  "psycopg >= 3.0.14; sys_platform != 'win32'",
34
34
  "psycopg-binary >= 3.0.14; sys_platform == 'win32'",
35
- "sqlparse >=0.3.0,<0.6",
35
+ "sqlparse >=0.3.0,<0.7",
36
36
  "configobj >= 5.0.6",
37
37
  "cli_helpers[styles] >= 2.4.0",
38
38
  # setproctitle is used to mask the password when running `ps` in command line.
@@ -73,9 +73,6 @@ build-backend = "setuptools.build_meta"
73
73
  [tool.setuptools]
74
74
  include-package-data = false
75
75
 
76
- [tool.setuptools.dynamic]
77
- version = { attr = "pgcli.__version__" }
78
-
79
76
  [tool.setuptools.packages]
80
77
  find = { namespaces = false }
81
78