Skip to main content

Write Excel XLSX declaratively.

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

poi-1.2.8.tar.gz (15.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

poi-1.2.8-py3-none-any.whl (17.8 kB view details)

Uploaded Python 3

File details

Details for the file poi-1.2.8.tar.gz.

File metadata

  • Download URL: poi-1.2.8.tar.gz
  • Upload date:
  • Size: 15.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for poi-1.2.8.tar.gz
Algorithm Hash digest
SHA256 0326a8629d0684347b0e578bcf41e3c87e1cdd18b911249a41fa532f54a7f7d7
MD5 ac7e919a1728545b2842aad2b44e5fd9
BLAKE2b-256 26b4bb0c55f8b67d2c30e8cffd7c4c9b4b14b48ffad434eadbf13bb570e86841

See more details on using hashes here.

Provenance

The following attestation bundles were made for poi-1.2.8.tar.gz:

Publisher: release.yml on ryanwang520/poi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file poi-1.2.8-py3-none-any.whl.

File metadata

  • Download URL: poi-1.2.8-py3-none-any.whl
  • Upload date:
  • Size: 17.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for poi-1.2.8-py3-none-any.whl
Algorithm Hash digest
SHA256 5a3b9d397a16e2aa045d12838c47db60c8e855d8a8f59e8557d25e94f9c7bb89
MD5 06f93c7a98b4fe2ae1ee080fed13147b
BLAKE2b-256 e19f5e30af6ab3c34932d292b00814e8e98e94f21ed648c3120fdc713ac74712

See more details on using hashes here.

Provenance

The following attestation bundles were made for poi-1.2.8-py3-none-any.whl:

Publisher: release.yml on ryanwang520/poi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page