power-pptx

Compose & Templates

power_pptx.compose covers JSON-first authoring with from_spec, and thePresentation methods import_slide / apply_template cover cross-presentation operations.

from_spec — JSON authoring

from power_pptx.compose import from_spec

prs = from_spec({
    "slides": [
        {"layout": "title",
         "title": "Q4 Review",
         "subtitle": "Engineering"},
        {"layout": "bullets",
         "title": "Highlights",
         "bullets": ["Shipped v2.10", "Lint pass clean"]},
        {"layout": "kpi",
         "title": "Run-rate metrics",
         "kpis": [{"label": "ARR", "value": "$4.2M", "delta": +0.08},
                  {"label": "NDR", "value": "131%", "delta": +0.03}]},
    ],
    "lint": "raise",       # built-in lint gate: raise | warn | off
})

prs.save("q4.pptx")

With "lint": "raise" in the spec the build fails fast on geometry errors — ideal in CI or agent pipelines where a broken deck is worse than no deck.

Free-standing shapes on a spec slide

Layouts and recipes place their own shapes. When you need something a layout doesn't provide, a slide entry may carry a "shapes" list, applied after the layout runs:

prs = from_spec({
    "slides": [
        {"layout": "blank",
         "shapes": [
            {"name": "card", "shape": "rounded_rectangle",
             "left": 1, "top": 1.4, "width": 4, "height": 2,
             "layer": "card"},
            {"name": "badge", "shape": "oval", "text": "NEW",
             "left": 4.4, "top": 1.0, "width": 1.2, "height": 0.8,
             "layer_above": "card"},
         ]},
    ],
})
KeyMeaning
left / top / width / heightRequired. Plain numbers are inches; pass a Length such as Inches(1.5) to opt out
nameThe shape's name, and the handle allow_overlap_with resolves against. Unique within the slide
shapeAn MSO_SHAPE member name, case- and separator-insensitive. Defaults to "textbox"
textText for the shape's text frame
lint_group / layer / layer_above / allow_overlap_withThe linter's intent declarations — see below

Unknown keys are rejected with a did-you-mean hint rather than silently ignored, and every error names the offending entry as slides[i].shapes[j]. This is deliberately minimal — geometry, type, text, intent. It is not a drawing DSL; reach for the Python API when you need fills, effects, or anything structural.

Declaring intentional overlaps in a spec

This is what makes the list more than a convenience: a generator — an LLM, most usefully — can declare at generation time that an overlap is deliberate, so the deck it emits lints clean with no manual tagging pass afterwards. All three mechanisms from thelinter are spec-level fields:

{"name": "badge", ..., "lint_group": "kpi-1"}          # n-ary tag
{"name": "badge", ..., "allow_overlap_with": "card"}   # one pair
{"name": "badge", ..., "allow_overlap_with": ["card", "rule"]}
{"name": "card",  ..., "layer": "card"}                # asserts z-order
{"name": "badge", ..., "layer_above": "card"}

allow_overlap_with names other shapes by their spec name, not by shape id — ids don't exist until the deck is built. Resolution happens in a second pass, after every shape on the slide exists, so a forward reference works: naming a shape defined later in the same list is fine. Names must be unique within a slide, and a reference may not cross slides, because an allowance is keyed on shape id and ids are only unique per slide. Both mistakes raise ValueError locating the bad entry.

import_slide — copy slides between decks

from power_pptx import Presentation

source = Presentation("template.pptx")
prs.import_slide(source.slides[2])      # deep-copies slide + related parts

Image renames, layout references and master/theme parts are handled automatically;merge_master="dedupe" (the default) reuses an equivalent master in the destination,"clone" always brings a fresh copy.

apply_template

prs.apply_template("corporate.potx")    # masters, layouts and theme

Re-points every slide's layout/master/theme at the template's masters (name → type → first layout matching); slide content is preserved and unreferenced old parts are dropped on save.

When to use which

NeedUse
Deck from LLM/DB/JSON outputfrom_spec
Reuse slides across decksimport_slide
Re-skin an existing deckapply_template
Re-palette + re-font onlytheme writer