Wrappers and Extenders

Companion Packages for Python Projects

Jules Walzer-Goldfeld

About Me

  • Software developer, tool builder
  • Author of gt-extras
  • Great Tables at Posit: Michael Chow and Richard Iannone
  • I enjoy πŸ“, ⚽, πŸ§—, 🚴

Biking

Roommate Story

Maintenance, Upgrades

You Borrow It

Solve It For Yourself

Modify The Bike For Everyone

Fork The Bike

Use An Optional Attachment

Developing Packages

Programming story

Maintenance, Upgrades

You Borrow It

shape: (9, 11)
Entity Code Year gini_disposable__age_total gini_market__age_total population_historical owid_region gini_pct_change gini_pct_benchmark_5yr pop_log pop_icons
str str i64 f64 f64 str str f64 f64 f64 i64
"France" "FRA" 2020 0.278 0.515 "65,905,226" "Europe" 46.02 null 7.81892 3
"Germany" "DEU" 2020 0.303 0.496 "83,628,661" "Europe" 38.91 40.966 7.922355 4
"South Korea" "KOR" 2020 0.328 0.405 "51,858,440" "Asia" 19.01 14.756 7.714819 1
"Spain" "ESP" 2020 0.329 0.515 "47,679,437" "Europe" 36.12 34.792 7.678331 1
"Italy" "ITA" 2020 0.331 0.527 "59,912,714" "Europe" 37.19 36.08 7.777519 2
"United Kingdom" "GBR" 2020 0.355 0.507 "67,351,806" "Europe" 29.98 29.332 7.828349 3
"United States" "USA" 2020 0.377 0.521 "339,436,106" "North America" 27.64 23.48 8.530758 11
"Turkey" "TUR" 2020 0.403 0.504 "86,091,644" "Asia" 20.04 16.208 7.934961 4
"Mexico" "MEX" 2020 0.42 0.435 "126,798,998" "North America" 3.45 3.35 8.103116 6
Income Inequality Before and After Taxes in 2020
gini_disposable__age_total gini_market__age_total Population Improvement Post Taxes gini_pct_benchmark_5yr
Europe
France 0.278 0.515 46.02 None
Germany 0.303 0.496 38.91 40.966
Spain 0.329 0.515 36.12 34.791999999999994
Italy 0.331 0.527 37.19 36.080000000000005
United Kingdom 0.355 0.507 29.98 29.332
Asia
South Korea 0.328 0.405 19.01 14.756
Turkey 0.403 0.504 20.04 16.208
North America
United States 0.377 0.521 27.64 23.48
Mexico 0.42 0.435 3.45 3.35
Income Inequality Before and After Taxes in 2020
Pre-tax to Post-tax Coefficient Population Improvement Post Taxes
Europe
France
0.520.28
65,905,226
Germany
0.50.3
83,628,661
Spain
0.520.33
47,679,437
Italy
0.530.33
59,912,714
United Kingdom
0.510.35
67,351,806
Asia
South Korea
0.410.33
51,858,440
Turkey
0.50.4
86,091,644
North America
United States
0.520.38
339,436,106
Mexico
0.430.42
126,798,998

Solve It For Yourself

Write It Locally

Modify The Bike For Everyone

Submit a PR For Everyone

Fork The Bike

Fork the Package

Use An Optional Attachment

Build A Companion Package

  • gt-extras + great_tables
  • nbmail or red-mail + smtplib
  • requests-toolbelt + requests

What Goes In The Package

What’s In A Package?

  1. 17th gear
  2. Night mountain ride
  3. Folding bike
  1. Wrapping
  2. Composing
  3. Extending

1) Wrapping

from great_tables import GT, html
from great_tables.data import airquality

airquality_mini = airquality.head(10).assign(Year=1973)

gt = (
    GT(airquality_mini)
    .tab_header(
        title="New York Air Quality Measurements",
        subtitle="Daily measurements in New York City (May 1-10, 1973)"
    )
    .tab_spanner(label="Time", columns=["Year", "Month", "Day"])
    .tab_spanner(
        label="Measurement",
        columns=["Ozone", "Solar_R", "Wind", "Temp"],
    )
    .cols_move_to_start(columns=["Year", "Month", "Day"])
    .cols_label(
        Ozone=html("Ozone,<br>ppbV"),
        Solar_R=html("Solar R.,<br>cal/m<sup>2</sup>"),
        Wind=html("Wind,<br>mph"),
        Temp=html("Temp,<br>&deg;F"),
    )
)

gt
New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 7.4 67
1973 5 2 36.0 118.0 8.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 8.6 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 8.6 69

1) Wrapping

New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 7.4 67
1973 5 2 36.0 118.0 8.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 8.6 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 8.6 69
New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 7.4 67
1973 5 2 36.0 118.0 8.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 8.6 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 8.6 69

1) Wrapping

New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 7.4 67
1973 5 2 36.0 118.0 8.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 8.6 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 8.6 69
from great_tables import loc, style

locations = [
    loc.body(columns="Wind"),
    loc.column_labels(columns="Wind"),
]

# Make the tab_style call
gt.tab_style(
    style=style.fill(color="lightblue"),
    locations=locations,
)
from great_tables import GT, loc, style

def highlight_columns(gt: GT, columns) -> GT:
    locations = [
        loc.body(columns=columns),
        loc.column_labels(columns=columns),
    ]

    # Make the wrapped tab_style call
    return gt.tab_style(
        style=style.fill(color="lightblue"),
        locations=locations,
    )

highlight_columns(gt, columns="Wind")

2) Composing

from great_tables import GT, html
from great_tables.data import airquality

airquality_mini = airquality.head(10).assign(Year=1973)

gt = (
    GT(airquality_mini)
    .tab_header(
        title="New York Air Quality Measurements",
        subtitle="Daily measurements in New York City (May 1-10, 1973)"
    )
    .tab_spanner(label="Time", columns=["Year", "Month", "Day"])
    .tab_spanner(
        label="Measurement",
        columns=["Ozone", "Solar_R", "Wind", "Temp"],
    )
    .cols_move_to_start(columns=["Year", "Month", "Day"])
    .cols_label(
        Ozone=html("Ozone,<br>ppbV"),
        Solar_R=html("Solar R.,<br>cal/m<sup>2</sup>"),
        Wind=html("Wind,<br>mph"),
        Temp=html("Temp,<br>&deg;F"),
    )
)

gt
New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 7.4 67
1973 5 2 36.0 118.0 8.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 8.6 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 8.6 69

2) Composing

New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 7.4 67
1973 5 2 36.0 118.0 8.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 8.6 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 8.6 69
New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 7.4 67
1973 5 2 36.0 118.0 8.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 8.6 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 8.6 69

2) Composing

New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 7.4 67
1973 5 2 36.0 118.0 8.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 8.6 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 8.6 69
def some_theme(gt: GT) -> GT:
    return (
        gt
        .opt_row_striping()
        .opt_table_font(font="Courier")
        .tab_options(
            heading_align="left",
            column_labels_text_transform="lowercase",
            row_striping_background_color="#b5dbb6",
        )
    )

gt.pipe(some_theme)

3) Extending

from great_tables import GT, html
from great_tables.data import airquality

airquality_mini = airquality.head(10).assign(Year=1973)

gt = (
    GT(airquality_mini)
    .tab_header(
        title="New York Air Quality Measurements",
        subtitle="Daily measurements in New York City (May 1-10, 1973)"
    )
    .tab_spanner(label="Time", columns=["Year", "Month", "Day"])
    .tab_spanner(
        label="Measurement",
        columns=["Ozone", "Solar_R", "Wind", "Temp"],
    )
    .cols_move_to_start(columns=["Year", "Month", "Day"])
    .cols_label(
        Ozone=html("Ozone,<br>ppbV"),
        Solar_R=html("Solar R.,<br>cal/m<sup>2</sup>"),
        Wind=html("Wind,<br>mph"),
        Temp=html("Temp,<br>&deg;F"),
    )
)

gt
New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 7.4 67
1973 5 2 36.0 118.0 8.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 8.6 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 8.6 69

3) Extending

New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 7.4 67
1973 5 2 36.0 118.0 8.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 8.6 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 8.6 69
New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 <10.0 67
1973 5 2 36.0 118.0 <10.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 <10.0 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 <10.0 69

3) Extending

New York Air Quality Measurements
Daily measurements in New York City (May 1-10, 1973)
Time Measurement
Year Month Day Ozone,
ppbV
Solar R.,
cal/m2
Wind,
mph
Temp,
Β°F
1973 5 1 41.0 190.0 <10.0 67
1973 5 2 36.0 118.0 <10.0 72
1973 5 3 12.0 149.0 12.6 74
1973 5 4 18.0 313.0 11.5 62
1973 5 5 14.3 56
1973 5 6 28.0 14.9 66
1973 5 7 23.0 299.0 <10.0 65
1973 5 8 19.0 99.0 13.8 59
1973 5 9 8.0 19.0 20.1 61
1973 5 10 194.0 <10.0 69
def fmt_threshold(gt, columns, threshold: float):

    def _truncate(val: float):
        if val < threshold:
            return f"<{threshold}"
        else:
            return str(val)

    return gt.fmt(_truncate, columns)

gt.pipe(fmt_threshold, "Wind", 10.0)

Finishing the Package

Package Structure

Metadata and source code

gt-extras
β”œβ”€β”€ pyproject.toml            # Metadata
└── gt_extras/                # Source code
    β”œβ”€β”€ __init__.py
    └── functions.py

Package Structure

Tests

gt-extras
β”œβ”€β”€ pyproject.toml            # Metadata
└── gt_extras/                # Source code
    β”œβ”€β”€ __init__.py
    β”œβ”€β”€ functions.py
    └── tests/                # Package tests

Package Structure

Documentation

gt-extras
β”œβ”€β”€ docs/                     # Package documentation
β”‚   β”œβ”€β”€ getting-started.md
β”‚   β”œβ”€β”€ examples.md
β”‚   └── gt_extras_reference.md
β”œβ”€β”€ LICENSE
β”œβ”€β”€ README.md
β”œβ”€β”€ Makefile                  # Metadata
β”œβ”€β”€ pyproject.toml
└── gt_extras/                # Source code
    β”œβ”€β”€ __init__.py
    β”œβ”€β”€ functions.py
    └── tests/                # Package tests

Package Structure

Continuous Integration / Continuous Development

gt-extras
β”œβ”€β”€ .github/                  # CI/CD
β”‚   β”œβ”€β”€ CONTRIBUTING.md
β”‚   └── workflows/
β”‚       β”œβ”€β”€ ci_build.yml
β”‚       └── ci_tests.yml
β”œβ”€β”€ docs/                     # Package documentation
β”‚   β”œβ”€β”€ getting-started.md
β”‚   β”œβ”€β”€ examples.md
β”‚   └── gt_extras_reference.md
β”œβ”€β”€ LICENSE
β”œβ”€β”€ README.md
β”œβ”€β”€ Makefile                  # Metadata
β”œβ”€β”€ pyproject.toml
└── gt_extras/                # Source code
    β”œβ”€β”€ __init__.py
    β”œβ”€β”€ functions.py
    └── tests/                # Package tests

Tests

Using pytest

import pytest
from great_tables import GT
from gt_extras import highlight_columns

Tests

Snapshot test

import pytest
from great_tables import GT
from gt_extras import highlight_columns

   
def test_highlight_columns_snap(snapshot, mini_gt):
    res = highlight_columns(mini_gt)
    assert_rendered_body(snapshot, gt=res)

Tests

Check content directly

import pytest
from great_tables import GT
from gt_extras import highlight_columns

   
def test_highlight_columns_snap(snapshot, mini_gt):
    res = highlight_columns(mini_gt)
    assert_rendered_body(snapshot, gt=res)


def test_highlight_columns(mini_gt):
    gt = highlight_columns(mini_gt, columns=[1, 2])
    html = gt.as_raw_html()
    assert html.count("lightblue") == num_rows(mini_gt) * 2

Documentation

Docstring

def highlight_columns(gt: GT, columns) -> GT:
    """
    Add color highlighting to one or more specific columns.

    Parameters
    ----------
    gt
        An existing `GT` object.

    columns
        The columns to target. Can be a single column or a list of columns (by name or index).
        If `None`, the coloring is applied to all columns.

Documentation

Return value

def highlight_columns(gt: GT, columns) -> GT:
    """
    Add color highlighting to one or more specific columns.

    Parameters
    ----------
    gt
        An existing `GT` object.

    columns
        The columns to target. Can be a single column or a list of columns (by name or index).
        If `None`, the coloring is applied to all columns.

    Returns
    -------
    GT
        The `GT` object is returned.

Documentation

Example

    """
    Parameters
    ----------
    gt
        An existing `GT` object.

    columns
        The columns to target. Can be a single column or a list of columns (by name or index).
        If `None`, the coloring is applied to all columns.

    Returns
    -------
    GT
        The `GT` object is returned.

    Example
    --------
    gt.pipe(gte.highlight_columns, columns="hp")
    """

CI/CD

Continuous Integration / Continuous Development

.github/workflows/*.yml

CI/CD

When to run it

name: Tests

on: [push, pull_request, release]

CI/CD

Write a job

name: Tests

on: [push, pull_request, release]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.12']
    steps:
      - name: Checkout
      - name: Install uv
      - name: Set up Python
      - name: Install the project
      - name: Test

CI/CD

Execute each step

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v5
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install the project
        run: uv sync --all-extras --dev
      - name: Test
        run: |
          uv run pytest

Configuration

pyproject.toml

[project]
name = "gt-extras"
version = "0.1.0"
description = "Provides additional functions for creating beautiful tables with 'great-tables'."

Configuration

Requirements

[project]
name = "gt-extras"
version = "0.1.0"
description = "Provides additional functions for creating beautiful tables with 'great-tables'."
requires-python = ">=3.9"
dependencies = [
    "great-tables>=0.18.0",
    "svg-py>=1.6.0",
]

Configuration

Other metadata

[project]
name = "gt-extras"
version = "0.1.0"
description = "Provides additional functions for creating beautiful tables with 'great-tables'."
requires-python = ">=3.9"
dependencies = [
    "great-tables>=0.18.0",
    "svg-py>=1.6.0",
]

[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra --cov=gt_extras --cov-report=term-missing"
testpaths = ["gt_extras/tests"]

keywords = ["tables", "html"]

We Talked About

We Talked About

We Talked About

We Talked About

Thank You

to Michael Chow and Richard Iannone (and the Posit family)