From 5339f6bd28efca7a2977324e2bfca5a2835f7340 Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Sat, 24 Jan 2026 09:51:21 -0500 Subject: [PATCH] configurable numeric alignment in tabular output After https://github.com/dbcli/cli_helpers/pull/97 we can set the alignment on a per-column basis using the colalign parameter. In this PR we upgrade cli_helpers and set colalign for columns with a numeric type. This is more performant than using tabulate's numparse capability, and unlike numparse, works on nullable numeric columns. The default alignment for numeric columns is changed from left to right, on the basis that this is the default for the vendor client. However, the user is free to change it back in ~/.myclirc . --- changelog.md | 8 ++++++++ mycli/main.py | 10 ++++++++++ mycli/myclirc | 3 +++ pyproject.toml | 2 +- test/myclirc | 3 +++ test/test_main.py | 2 +- 6 files changed, 26 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index c8839328..b961d394 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,11 @@ +TBD +============== + +Features +-------- +* Right-align numeric columns, and make the behavior configurable. + + 1.47.0 (2026/01/24) ============== diff --git a/mycli/main.py b/mycli/main.py index 006c7f69..9514f613 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections import defaultdict, namedtuple +from decimal import Decimal from io import TextIOWrapper import logging import os @@ -160,6 +161,7 @@ def __init__( self.login_path_as_host = c["main"].as_bool("login_path_as_host") self.post_redirect_command = c['main'].get('post_redirect_command') self.null_string = c['main'].get('null_string') + self.numeric_alignment = c['main'].get('numeric_alignment', 'right') # set ssl_mode if a valid option is provided in a config file, otherwise None ssl_mode = c["main"].get("ssl_mode", None) @@ -831,6 +833,7 @@ def output_res(results: Generator[SQLResult], start: float) -> None: special.is_expanded_output(), special.is_redirected(), self.null_string, + self.numeric_alignment, max_width, ) @@ -868,6 +871,7 @@ def output_res(results: Generator[SQLResult], start: float) -> None: special.is_expanded_output(), special.is_redirected(), self.null_string, + self.numeric_alignment, max_width, ) self.echo("") @@ -1345,6 +1349,7 @@ def run_query( special.is_expanded_output(), special.is_redirected(), self.null_string, + self.numeric_alignment, ) for line in output: self.log_output(line) @@ -1364,6 +1369,7 @@ def run_query( special.is_expanded_output(), special.is_redirected(), self.null_string, + self.numeric_alignment, ) for line in output: click.echo(line, nl=new_line) @@ -1379,6 +1385,7 @@ def format_output( expanded: bool = False, is_redirected: bool = False, null_string: str | None = None, + numeric_alignment: str = 'right', max_width: int | None = None, ) -> itertools.chain[str]: if is_redirected: @@ -1408,6 +1415,7 @@ def format_output( if headers or (cur and title): column_types = None + colalign = None if isinstance(cur, Cursor): def get_col_type(col) -> type: @@ -1415,6 +1423,7 @@ def get_col_type(col) -> type: return col_type if type(col_type) is type else str column_types = [get_col_type(tup) for tup in cur.description] + colalign = [numeric_alignment if x in (int, float, Decimal) else 'left' for x in column_types] if max_width is not None and isinstance(cur, Cursor): cur = list(cur) @@ -1424,6 +1433,7 @@ def get_col_type(col) -> type: headers, format_name="vertical" if expanded else None, column_types=column_types, + colalign=colalign, **output_kwargs, ) diff --git a/mycli/myclirc b/mycli/myclirc index 66ac242d..91d92294 100644 --- a/mycli/myclirc +++ b/mycli/myclirc @@ -73,6 +73,9 @@ redirect_format = csv # empty string, and JSON formats use native nulls. null_string = +# How to align numeric data in tabular output: right or left. +numeric_alignment = right + # A command to run after a successful output redirect, with {} to be replaced # with the escaped filename. Mac example: echo {} | pbcopy. Escaping is not # reliable/safe on Windows. diff --git a/pyproject.toml b/pyproject.toml index 1f4b6e55..8beb9cd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "sqlparse>=0.3.0,<0.6.0", "sqlglot[rs] == 27.*", "configobj >= 5.0.5", - "cli_helpers[styles] >= 2.7.0", + "cli_helpers[styles] >= 2.8.0", "pyperclip >= 1.8.1", "pycryptodomex", "pyfzf >= 0.3.1", diff --git a/test/myclirc b/test/myclirc index d4061fa5..870ef552 100644 --- a/test/myclirc +++ b/test/myclirc @@ -71,6 +71,9 @@ redirect_format = csv # empty string, and JSON formats use native nulls. null_string = +# How to align numeric data in tabular output: right or left. +numeric_alignment = right + # A command to run after a successful output redirect, with {} to be replaced # with the escaped filename. Mac example: echo {} | pbcopy. Escaping is not # reliable/safe on Windows. diff --git a/test/test_main.py b/test/test_main.py index 66a2ef85..22ab2c99 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -438,7 +438,7 @@ def test_batch_mode_table(executor): +----------+ | count(*) | +----------+ - | 3 | + | 3 | +----------+ +-----+ | a |