Skip to main content

carbotek

CI PyPI Python Rust License
Ruff uv Checked with pyright Architecture: tach Typed Carbone syntax

English | Русский

Render DOCX and XLSX templates with Carbone-style markers ({d.name}, loops, conditions, formatters, images, barcodes, HTML, hyperlinks, colors) into DOCX, XLSX or PDF from Python. There is no LibreOffice: PDF is produced by dxpdf.

carbotek is a Python port of the DOCX and XLSX engine of Carbone v3.2.3, the last Carbone release under the Apache License 2.0. The template syntax is compatible with Carbone 5.14.4. Behaviour was checked against the JavaScript engine with a differential corpus and formatter oracles.

carbotek is an independent project and is not affiliated with or endorsed by CarboneIO SAS. "Carbone" is used only to describe compatibility.

Licensed under the PolyForm Noncommercial 1.0.0 license. It is free for noncommercial use; commercial use requires a separate license (see LICENSE).

Installation

pip install carbotek        # or: uv add carbotek

Requires CPython 3.15+. The primary target is Linux in Docker (python:3.15-slim-trixie); CI runs the test suite against the released wheel inside that image.

Linux wheels (manylinux, x86_64 and aarch64, one abi3 wheel for CPython 3.15+) contain an optional native accelerator (carbotek._native, Rust) that renders common loop tables and money formatting faster; the output is identical to the pure-Python path. A source-distribution install builds the accelerator and needs a Rust toolchain (1.87+). Set CARBOTEK_NATIVE=0 to render without it, or CARBOTEK_NATIVE=1 to make a missing extension an import error; the variable is read once, when carbotek is imported.

A universal py3-none-any wheel is also published for environments without Rust. Pip prefers an available platform wheel; select the universal wheel explicitly to install only the Python renderer. From an unpacked source distribution, build it with uv run --no-project --with build --with setuptools --with wheel python tools/build_pure_wheel.py --out dist.

Library

import json
import carbotek

template = open("invoice.docx", "rb").read()
data = json.load(open("data.json", encoding="utf-8"))

docx = carbotek.render(template, data, {"lang": "en", "timezone": "Europe/London"})
pdf = carbotek.render_pdf(template, data, image_dpi=220)   # DOCX templates only

engine = carbotek.Engine(cache_size=16)   # own compiled-template cache
engine.render(template, data)
print(engine.metrics)                     # size, bytes, hits, misses, hit_rate

carbotek.render detects the template type: a DOCX template returns DOCX, an XLSX template returns XLSX. The module-level functions share one default Engine.

Options

Options are the keys of RenderOptions.create:

Key Meaning
lang Document language (default ru)
timezone IANA time zone (default Europe/Moscow)
complement {c.*} data
translations {t(...)} dictionaries
enum Enumerations for convEnum
currency {"source", "target", "rates"}
variable_str Template variables
use_high_precision_arithmetic High-precision arithmetic
export_formatted_values_as_text XLSX: keep formatted numbers as text

Russian formatters

carbotek adds formatters for Russian business documents (they are not part of Carbone):

Formatter (aliases) Example Result
digitCurrency (numCurrency, numberCurrency, digitalCurrency) {d.a:digitCurrency}, a = 1234567.5 1 234 567,50
{d.a:digitCurrency(0)} 1 234 568
formatAmount {d.a:formatAmount}, a = 1234.5 1 234,50
wordNumber (words, numberToWords) {d.a:wordNumber}, a = 1234 одна тысяча двести тридцать четыре
wordCurrency {d.a:wordCurrency}, a = 1234.56 Одна тысяча двести тридцать четыре рубля 56 копеек
moneyFull (sumFull) {d.a:moneyFull}, a = 1234.56 1 234,56 (Одна тысяча двести тридцать четыре рубля 56 копеек)
procent (percent) {d.a:procent}, a = 20 20,00 %
dashIfZero (dashZero) {d.a:dashIfZero}, a = 0 —
plural (pluralize) {d.n:plural('день','дня','дней')}, n = 5 5 дней
{d.n:plural('день','дня','дней', false)}, n = 21 день
ruDate {d.dt:ruDate}, dt = 2026-10-11 «11» октября 2026
roundHalfUp {d.a:roundHalfUp(1)}, a = 2.25 2.3
parseAmount {d.s:parseAmount}, s = 1 234,50 1234.5
parseDate {d.s:parseDate}, s = 11.10.2026 2026-10-11
capitalize (firstUpper) {d.s:capitalize}, s = иванов Иванов

Amount formatters take an optional precision (default 2) and round half up. ruDate uses the render time zone, or the one given as an argument.

Errors

All template and data errors are subclasses of carbotek.CarbotekError. Each error has a stable code:

Code Error
VALIDATION_ERROR Invalid input
TEMPLATE_ERROR Invalid template, marker or formatter
FEATURE_UNAVAILABLE Unsupported feature
CONVERTER_ERROR PDF conversion failed

Messages follow the JS engine where applicable; input and infrastructure failures have library-specific messages.

Images from URLs and files

By default, only data URIs are loaded. Remote and local images have to be enabled explicitly:

from carbotek.features.fetch import ImageSourceConfig

engine = carbotek.Engine(image_sources=ImageSourceConfig(
    allowlist=("cdn.example.com",),   # hosts allowed for http(s) images; private addresses are refused
    file_root="/srv/images",          # file:// images are read only under this directory
    max_bytes=10_485_760,
    fetch_timeout_ms=5000,
    max_download_concurrency=4,
    total_timeout_ms=10000,
))

Without image_sources, the same settings are read from environment variables:

  • CARBOTEK__IMAGE_URL_ALLOWLIST (comma-separated)
  • CARBOTEK__IMAGE_FILE_ROOT
  • CARBOTEK__IMAGE_MAX_BYTES
  • CARBOTEK__IMAGE_FETCH_TIMEOUT_MS
  • CARBOTEK__IMAGE_MAX_DOWNLOAD_CONCURRENCY
  • CARBOTEK__IMAGE_TOTAL_TIMEOUT_MS

Environment settings are snapshotted once at the start of each render. Changes during a render affect the next call; an explicit ImageSourceConfig takes precedence. Image limits must be positive integers; invalid environment limits raise ValidationError. Explicit configuration also validates field types and copies/normalizes the allowlist.

XLSX

XLSX templates render to XLSX the same way as in Carbone. Supported:

  • markers;
  • loops over rows and cells, including nested loops;
  • conditions and conditional blocks;
  • drop/keep (row, col, table);
  • aggregates and variables.

The workbook is prepared as in the JS engine:

  • calcChain.xml is removed;
  • shared strings become inline strings;
  • a cell whose only content is a :formatN marker becomes numeric. To keep it as text, use the export_formatted_values_as_text option or {o.exportFormattedValuesAsText=true}.

Markers work anywhere in the workbook: sheet names, headers and footers, defined names, comments.

  • Pictures: put a marker in the alt text of a sheet picture, for example {d.logo} or {d.code:barcode(ean13)}.
  • Hyperlinks: a hyperlink target https://d.site becomes {d.site:validateURL}.
  • Not recalculated: formulas, dimension and merged cells.
  • No PDF output: PDF from XLSX raises FeatureUnavailable.

CLI

carbotek render template.docx data.json -o out.docx
carbotek render template.xlsx data.json                    # -> template.out.xlsx
carbotek render template.docx data.json --pdf -o out.pdf --image-dpi 300
carbotek bench  template.docx data.json -n 50              # warm cache: cold, mean, p50, p95, min, max, per second
carbotek bench  template.docx data.json --no-cache --json   # reset module caches and registry for each render
carbotek markers template.docx                             # markers per part after validated compilation

python -m carbotek ... works too.

Common flags:

  • --lang, --timezone;
  • --options '{"complement": {"company": "X"}}';
  • - instead of the data file reads data from stdin.

Using it in a web service / task queue

The API is synchronous and CPU-bound. It takes bytes and returns bytes, and does no I/O except the optional image downloads.

  • Create one Engine per process and reuse it, so compiled templates are cached. Engine is thread-safe.
  • Do not call it on the event loop.
    • In an async framework, use await asyncio.to_thread(engine.render, template, data, options).
    • For a task queue (for example taskiq or Celery), a synchronous task with process-based workers scales best, because rendering holds the GIL.
  • Map CarbotekError to client errors (4xx) by its code. Any other exception is a server error.
  • Install the template fonts in the container (or metric-compatible fonts such as Carlito/Caladea). PDF uses system fonts.
import asyncio
import carbotek

engine = carbotek.Engine(cache_size=64)

async def render_document(template: bytes, data: dict, pdf: bool) -> bytes:
    if pdf:
        return await asyncio.to_thread(engine.render_pdf, template, data)
    return await asyncio.to_thread(engine.render, template, data)

Limitations

  • ECharts charts are not supported. A {d.x:chart} marker raises FeatureUnavailable. Native Word/Excel charts are updated with the data.
  • PDF has no bookmark outline and no document information (title, author).
  • Native OOXML charts are not drawn in PDF. Their data in the DOCX is still updated.
  • WMF/EMF images are drawn only partially in PDF. A warning is logged.
  • The table of contents is not recalculated. Page numbers come from the template.
  • An even/odd-page section break acts like nextPage.

Differences from Carbone

Deliberate, documented differences from the JavaScript engine:

  • A {o.*} option split across runs by Word is applied (Carbone leaves it in the text).
  • An object or array printed by a marker uses JavaScript string conversion ([object Object], 1,2).
  • A named sort combined with a repeater renders (Carbone fails to compile that template).
  • An empty native chart series is written as ptCount=0, as OOXML requires.
  • Items without a named sort key keep their input order.
  • Each picture keeps its own frame size when several pictures use the same image marker (Carbone applies the size of the last of them to all).
  • Native wheels are built only for Linux; other platforms install the pure-Python wheel, which is not tested there.

Development

uv sync                      # builds the native extension; needs the Rust toolchain from rust-toolchain.toml
uv run ruff check . && uv run ruff format --check .
uv run pyright               # strict mode, per-file headers
uv run tach check            # layer boundaries, see tach.toml
uv run pytest -n auto

Layers, from top to bottom: cli → api → pipeline, opc, pdf → features, xlsx → docx → markers, formatters → package, ooxml, options. A module may import only lower layers; tach check enforces it.

Security issues: see SECURITY.md.

License

carbotek is licensed under the PolyForm Noncommercial License 1.0.0. Copyright (c) 2026 Carbotek.

It is derived from Carbone v3.2.3, Copyright (c) CarboneIO SAS and contributors, licensed under the Apache License 2.0. The original code was translated from JavaScript to Python and Rust and substantially modified. See NOTICE and THIRD_PARTY_LICENSES.md.

Metadata

Release files for carbotek 0.1.0

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

Source distribution (sdist)

Source distribution for carbotek 0.1.0
File Size Uploaded
carbotek-0.1.0.tar.gz 497.3 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for carbotek 0.1.0
File Interpreter ABI Platform
carbotek-0.1.0-py3-none-any.whl Python 3 none any Details
carbotek-0.1.0-cp315-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.15 abi3 Linux glibc 2.17+ x86-64 Details
carbotek-0.1.0-cp315-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.15 abi3 Linux glibc 2.17+ ARM64 Details

Total release size: 2.7 MB

Release files / carbotek-0.1.0.tar.gz

Download URL carbotek-0.1.0.tar.gz
Size 497.3 kB
Tags Source
SHA-256 checksum
How to use checksums
a9c861432dbd63bdce1855b3e9f96facce210ddbd334bf35effff848c177444e
BLAKE2b-256 checksum
How to use checksums
38dfcc549c7629751e0b8368efc6078820a27c83e07bda980baf052a41410a33
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 10, 2026.

Transparency log

Release files / carbotek-0.1.0-py3-none-any.whl

Download URL carbotek-0.1.0-py3-none-any.whl
Size 514.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b786a89edef59d5cabf44ff5f0b33aff573117f95bc7235e3ef0d0e521f8636c
BLAKE2b-256 checksum
How to use checksums
6eb5283bd2068adbf36bb1a8ac2d79a632f1a534beb89ec4e6b39e3e8e8fe40e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 10, 2026.

Transparency log

Release files / carbotek-0.1.0-cp315-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL carbotek-0.1.0-cp315-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 831.2 kB
Tags CPython 3.15 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
c6edcf53c2436eeea43bde9a2be7ff47be8e14c76d047569ad9bffa7dea665bc
BLAKE2b-256 checksum
How to use checksums
4459af18fd9dca1384fbb0fa80bb638d9e17e88c9b95efd1f13365e25dfcf02e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 10, 2026.

Transparency log

Release files / carbotek-0.1.0-cp315-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL carbotek-0.1.0-cp315-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 825.4 kB
Tags CPython 3.15 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
e5b365bf0a150b9c02d85b31a7436e83b0315522452c62cb9cffbddb719d9d9f
BLAKE2b-256 checksum
How to use checksums
97764596e62cc34409c0c2e06355c439f06ddb598c4f7b4169e3a230436b1680
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 10, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.1

4 release files

0.2.0

4 release files

This release

0.1.0 This release

4 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