Skip to content

Commit 5339f6b

Browse files
committed
configurable numeric alignment in tabular output
After dbcli/cli_helpers#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 .
1 parent 2d97cd2 commit 5339f6b

6 files changed

Lines changed: 26 additions & 2 deletions

File tree

changelog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
TBD
2+
==============
3+
4+
Features
5+
--------
6+
* Right-align numeric columns, and make the behavior configurable.
7+
8+
19
1.47.0 (2026/01/24)
210
==============
311

mycli/main.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from collections import defaultdict, namedtuple
4+
from decimal import Decimal
45
from io import TextIOWrapper
56
import logging
67
import os
@@ -160,6 +161,7 @@ def __init__(
160161
self.login_path_as_host = c["main"].as_bool("login_path_as_host")
161162
self.post_redirect_command = c['main'].get('post_redirect_command')
162163
self.null_string = c['main'].get('null_string')
164+
self.numeric_alignment = c['main'].get('numeric_alignment', 'right')
163165

164166
# set ssl_mode if a valid option is provided in a config file, otherwise None
165167
ssl_mode = c["main"].get("ssl_mode", None)
@@ -831,6 +833,7 @@ def output_res(results: Generator[SQLResult], start: float) -> None:
831833
special.is_expanded_output(),
832834
special.is_redirected(),
833835
self.null_string,
836+
self.numeric_alignment,
834837
max_width,
835838
)
836839

@@ -868,6 +871,7 @@ def output_res(results: Generator[SQLResult], start: float) -> None:
868871
special.is_expanded_output(),
869872
special.is_redirected(),
870873
self.null_string,
874+
self.numeric_alignment,
871875
max_width,
872876
)
873877
self.echo("")
@@ -1345,6 +1349,7 @@ def run_query(
13451349
special.is_expanded_output(),
13461350
special.is_redirected(),
13471351
self.null_string,
1352+
self.numeric_alignment,
13481353
)
13491354
for line in output:
13501355
self.log_output(line)
@@ -1364,6 +1369,7 @@ def run_query(
13641369
special.is_expanded_output(),
13651370
special.is_redirected(),
13661371
self.null_string,
1372+
self.numeric_alignment,
13671373
)
13681374
for line in output:
13691375
click.echo(line, nl=new_line)
@@ -1379,6 +1385,7 @@ def format_output(
13791385
expanded: bool = False,
13801386
is_redirected: bool = False,
13811387
null_string: str | None = None,
1388+
numeric_alignment: str = 'right',
13821389
max_width: int | None = None,
13831390
) -> itertools.chain[str]:
13841391
if is_redirected:
@@ -1408,13 +1415,15 @@ def format_output(
14081415

14091416
if headers or (cur and title):
14101417
column_types = None
1418+
colalign = None
14111419
if isinstance(cur, Cursor):
14121420

14131421
def get_col_type(col) -> type:
14141422
col_type = FIELD_TYPES.get(col[1], str)
14151423
return col_type if type(col_type) is type else str
14161424

14171425
column_types = [get_col_type(tup) for tup in cur.description]
1426+
colalign = [numeric_alignment if x in (int, float, Decimal) else 'left' for x in column_types]
14181427

14191428
if max_width is not None and isinstance(cur, Cursor):
14201429
cur = list(cur)
@@ -1424,6 +1433,7 @@ def get_col_type(col) -> type:
14241433
headers,
14251434
format_name="vertical" if expanded else None,
14261435
column_types=column_types,
1436+
colalign=colalign,
14271437
**output_kwargs,
14281438
)
14291439

mycli/myclirc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ redirect_format = csv
7373
# empty string, and JSON formats use native nulls.
7474
null_string = <null>
7575

76+
# How to align numeric data in tabular output: right or left.
77+
numeric_alignment = right
78+
7679
# A command to run after a successful output redirect, with {} to be replaced
7780
# with the escaped filename. Mac example: echo {} | pbcopy. Escaping is not
7881
# reliable/safe on Windows.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies = [
1717
"sqlparse>=0.3.0,<0.6.0",
1818
"sqlglot[rs] == 27.*",
1919
"configobj >= 5.0.5",
20-
"cli_helpers[styles] >= 2.7.0",
20+
"cli_helpers[styles] >= 2.8.0",
2121
"pyperclip >= 1.8.1",
2222
"pycryptodomex",
2323
"pyfzf >= 0.3.1",

test/myclirc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ redirect_format = csv
7171
# empty string, and JSON formats use native nulls.
7272
null_string = <nope>
7373

74+
# How to align numeric data in tabular output: right or left.
75+
numeric_alignment = right
76+
7477
# A command to run after a successful output redirect, with {} to be replaced
7578
# with the escaped filename. Mac example: echo {} | pbcopy. Escaping is not
7679
# reliable/safe on Windows.

test/test_main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ def test_batch_mode_table(executor):
438438
+----------+
439439
| count(*) |
440440
+----------+
441-
| 3 |
441+
| 3 |
442442
+----------+
443443
+-----+
444444
| a |

0 commit comments

Comments
 (0)