Skip to main content

Poi

Declarative Excel for Python. Build complex spreadsheets like you build React components — no coordinates, no merge_range math.

CI PyPI Python License Docs


Before & After

A simple report: a title row spanning four columns, two KPI cards, and a data table below.

With xlsxwriter — you manage every coordinate:

import xlsxwriter

wb = xlsxwriter.Workbook("report.xlsx")
ws = wb.add_worksheet()

title  = wb.add_format({"bold": True, "font_size": 18, "align": "center"})
kpi    = wb.add_format({"bold": True, "align": "center", "border": 1, "font_size": 14})
header = wb.add_format({"bold": True, "bg_color": "#EFEFEF", "border": 1})
cell   = wb.add_format({"border": 1})

ws.merge_range("A1:D1", "Q4 Report", title)
ws.merge_range("A3:B5", "Revenue $1.28M", kpi)
ws.merge_range("C3:D5", "Growth +18%", kpi)

for col, h in enumerate(["Region", "Q3", "Q4", "Δ"]):
    ws.write(6, col, h, header)

for row, r in enumerate(
    [("North", 320, 410, "+28%"), ("South", 280, 305, "+9%")], start=7
):
    for col, val in enumerate(r):
        ws.write(row, col, val, cell)

wb.close()

With Poi — you describe the structure:

from poi import Sheet, Col, Row, Cell, Table

regions = [
    {"region": "North", "q3": 320, "q4": 410, "delta": "+28%"},
    {"region": "South", "q3": 280, "q4": 305, "delta": "+9%"},
]

Sheet(root=Col(
    Cell("Q4 Report", colspan=4, style="font_size: 18; bold: true; align: center"),
    Row(
        Cell("Revenue\n$1.28M", colspan=2, style="border: 1; bold: true"),
        Cell("Growth\n+18%",    colspan=2, style="border: 1; bold: true"),
    ),
    Table(
        data=regions,
        columns=[("region", "Region"), ("q3", "Q3"), ("q4", "Q4"), ("delta", "Δ")],
        border=1,
    ),
)).write("report.xlsx")

No row indexes. No merge_range strings. Insert a new section anywhere — everything below shifts automatically.


Quick Start

pip install poi
from poi import Sheet, Table

data = [
    {"name": "iPhone", "price": 999},
    {"name": "iPad",   "price": 599},
]

Sheet(root=Table(
    data=data,
    columns=[("name", "Product"), ("price", "Price")],
    col_width="auto",
    border=1,
)).write("products.xlsx")

When to Use Poi

Poi is built for spreadsheets that aren't just "dump a dataframe":

  • Dashboard reports with KPI cards stacked above detailed tables
  • Multi-section sheets where the layout shifts as data changes
  • CJK-heavy layouts where column widths actually matter
  • Multi-sheet books with mixed structures across tabs

If df.to_excel() is good enough for you, you don't need Poi. If you've ever counted rows by hand or rewritten merge_range("A3:B5", ...) because you inserted a row — Poi is for you.


Smart CJK Column Auto-fit

A real differentiator. Most Excel libraries measure column width by character count, which silently breaks for Chinese, Japanese, and Korean text — each CJK character renders at roughly twice the width of an ASCII character.

Table(data=data, columns=[...], col_width="auto")

Poi counts double-width characters correctly and recognizes common date patterns. Your 产品名称 column fits on the first try.


Full Example: Images & Conditional Formatting

from typing import NamedTuple
from datetime import datetime
from poi import Sheet, Table


class Product(NamedTuple):
    name: str
    price: int
    created_at: datetime
    img: str


data = [
    Product(f"prod {i}", i * 17, datetime.now(), "./product.jpg")
    for i in range(5)
]

Sheet(root=Table(
    data=data,
    columns=[
        {"type": "image", "attr": "img", "title": "Image",
         "options": {"x_scale": 0.27, "y_scale": 0.25}},
        ("name", "Name"),
        ("price", "Price"),
        ("created_at", "Created"),
    ],
    col_width="auto",
    row_height=80,
    cell_style={
        # Bold red when price > 50
        "font_color: red; bold: true":
            lambda record, col: col.attr == "price" and record.price > 50,
    },
    date_format="yyyy-mm-dd",
    align="center",
    border=1,
)).write("table.xlsx")

table


Comparison

Poi xlsxwriter openpyxl pandas.to_excel xltpl
Style Declarative tree Imperative coordinates Imperative coordinates One-shot export Excel as template
Nested layouts (KPI cards above tables) Native (Row/Col) Manual offset math Manual offset math Not supported Constrained by template
Coordinate / span math Automatic You compute You compute N/A Template-bound
CJK-aware column auto-fit Built in (col_width="auto") Manual set_column Manual Not supported Template-bound
Conditional cell style Lambda per row Manual per cell Manual per cell Verbose Styler API Pre-baked in template
Multi-sheet books Book API Supported Supported ExcelWriter Supported
Best when Designed reports & dashboards Maximum control, very large writes Reading + editing existing files Pure dataframe dump Fixed layouts filled with data

Features

  • 🎨 Declarative DSL — describe spreadsheets as a tree of Row, Col, Cell, Image, Table
  • 📐 Automatic layout — rowspans, colspans, offsets, and indexes are computed for you
  • 📏 Smart column auto-fitcol_width="auto", native CJK & date-pattern support
  • 🎯 CSS-like stylingstyle="font_size: 14; bold: true; bg_color: #EEE"
  • 🎛️ Lambda-based conditional formatting — style cells by predicate
  • 💬 Interactive comments — pop-up tooltips on cells and headers
  • 📚 Multi-sheet workbooks via the Book API
  • Fast — built on xlsxwriter; fast=True skips empty-cell styling for very large outputs

Documentation

📖 Read the docs →


Contributing

Poi is a small, focused library and feedback is very welcome — especially from teams using it on real Chinese / multilingual reports. Open an issue, share a tricky layout, or send a PR.


Why "Poi"? A short, easy-to-type name — like a Point on a grid. That's it.

Release files for poi 1.2.8

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for poi 1.2.8
File Size Uploaded
poi-1.2.8.tar.gz 15.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for poi 1.2.8
File Interpreter ABI Platform
poi-1.2.8-py3-none-any.whl Python 3 none any Details

Total release size: 32.9 kB

Release files / poi-1.2.8.tar.gz

Download URL poi-1.2.8.tar.gz
Size 15.1 kB
Tags Source
SHA-256 checksum
How to use checksums
0326a8629d0684347b0e578bcf41e3c87e1cdd18b911249a41fa532f54a7f7d7
BLAKE2b-256 checksum
How to use checksums
26b4bb0c55f8b67d2c30e8cffd7c4c9b4b14b48ffad434eadbf13bb570e86841
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 21, 2026.

Transparency log

Release files / poi-1.2.8-py3-none-any.whl

Download URL poi-1.2.8-py3-none-any.whl
Size 17.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5a3b9d397a16e2aa045d12838c47db60c8e855d8a8f59e8557d25e94f9c7bb89
BLAKE2b-256 checksum
How to use checksums
e19f5e30af6ab3c34932d292b00814e8e98e94f21ed648c3120fdc713ac74712
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.2.8 This release

2 release files

1.2.7

2 release files

1.2.6

2 release files

1.2.5

2 release files

1.2.4

2 release files

1.2.3

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.7

2 release files

1.1.6

2 release files

1.1.5

2 release files

1.1.4

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.3.0

2 release files

0.2.9

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.23

2 release files

0.1.22

2 release files

0.1.21

2 release files

0.1.19

1 release file

0.1.18

2 release files

0.1.15

2 release files

0.1.14

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page