Skip to main content

Drop-in replacement for python-pptx that connects to PPTX Studio for real-time collaboration

Project description

athena-python-pptx

A drop-in replacement for python-pptx 1.0.2 that connects to PPTX Studio for real-time collaboration.

Use the exact same imports and API as python-pptx — every canonical import path resolves, every public class is present, every enum value matches:

from pptx import Presentation
from pptx.util import Inches, Pt, Cm
from pptx.enum.shapes import MSO_SHAPE
from pptx.dml.color import RGBColor
from pptx.chart.data import CategoryChartData
from pptx.chart.axis import CategoryAxis, ValueAxis
from pptx.shapes.autoshape import Shape
from pptx.text.text import Font, TextFrame

⚠️ Package conflict with python-pptx

athena-python-pptx and the upstream python-pptx package cannot coexist in the same Python environment. Both distribute files under the top-level pptx import. pip install athena-python-pptx does not automatically uninstall python-pptx — instead, pip silently overwrites the conflicting files. Both distributions remain registered as "installed", but the actual pptx module on disk ends up as a corrupted mix of files from both wheels. The result is an environment that imports successfully but raises AttributeError on calls that exist in one library but not the other.

The only safe configuration is one library per environment; always use an isolated virtualenv / venv to keep them apart.

Why this happens

This SDK is designed to be a drop-in replacement for python-pptx — every from pptx import … statement should resolve to a remote-aware proxy class that talks to PPTX Studio instead of a local OOXML adapter. The only way to preserve that contract is to shadow the pptx top-level package, which forces a hard either/or choice at install time. There is no "use both at once" mode.

Workaround: separate virtualenvs

If you need both libraries (for example, to read a stock .pptx from disk and mutate an Athena-hosted deck in the same workflow), put each in its own virtualenv and shuttle data between them as bytes / JSON / inline arguments — never both in the same sys.path.

# Venv 1: stock python-pptx for local file I/O
python -m venv .venv-stock
source .venv-stock/bin/activate
pip install python-pptx
deactivate

# Venv 2: athena-python-pptx for remote authoring
python -m venv .venv-athena
source .venv-athena/bin/activate
pip install athena-python-pptx
deactivate

Recipe: "read a stock .pptx, write to Athena"

The most common cross-venv pipeline is to extract content from a local .pptx with the stock library, then push it into an Athena deck through this SDK. Two patterns work:

Pattern A — upload the file directly. If the local PPTX is already in the shape you want, skip stock python-pptx entirely:

# In the .venv-athena venv:
from pptx import Presentation
prs = Presentation.upload("local_deck.pptx")  # ingests through Athena
# …mutate via the SDK, then prs.save("out.pptx") to download.

Pattern B — extract with stock, author with Athena. When you need to read structure (e.g., pull bullet text out of an existing deck) before rewriting it, run two scripts:

# extract.py — runs in .venv-stock
from pptx import Presentation
import json

prs = Presentation("local_deck.pptx")
payload = [
    {"slide": i, "title": (s.shapes.title.text if s.shapes.title else None)}
    for i, s in enumerate(prs.slides)
]
with open("/tmp/extract.json", "w") as f:
    json.dump(payload, f)
# author.py — runs in .venv-athena
from pptx import Presentation
import json

with open("/tmp/extract.json") as f:
    payload = json.load(f)

prs = Presentation.create(name="Imported deck")
for entry in payload:
    slide = prs.slides.add_slide()
    if entry["title"] and slide.shapes.title is not None:
        slide.shapes.title.text = entry["title"]
prs.close()

Invoke them from a shell wrapper that activates the right venv for each step. Cross-venv state must always travel through a serialized intermediate (JSON, pickled bytes, a shared file) — never via shared Python imports.


Parity status (v0.1.74)

Dimension Coverage
Upstream public classes 150 / 150 (100%, 0 uncovered)
Upstream enum members + integer values 14 / 14 enums verified byte-for-byte
Canonical from pptx.X import Y paths from python-pptx docs 57 / 57 resolve
SDK behavioural tests 1048 passing, 0 failing

See API_PARITY_REPORT.md for the per-class status table, docs/API_PARITY_EXCEPTIONS.md for documented REST-SDK departures, and PARITY_QUESTIONS.md for the open-question / followup tracker.

Features

  • python-pptx compatible imports - from pptx import Presentation
  • Same API - Familiar interface for developers and LLMs
  • Real-time collaboration - Changes sync instantly with web UI
  • Remote-first - Edits apply to live Yjs documents via API
  • Athena Extensions - Additional features not in python-pptx (render to PNG, batch delete, clone slides)
  • Clear error messages - Unimplemented features raise helpful errors

Note: Like python-pptx, slides are zero-indexed. When a user says "slide 1", access it as prs.slides[0].

Installation

pip install athena-python-pptx

Configuration

The SDK uses environment variables for configuration (recommended):

export ATHENA_PPTX_BASE_URL=https://api.pptx-studio.com
export ATHENA_PPTX_API_KEY=your-api-key  # Optional

Or pass them explicitly:

prs = Presentation(asset_id="asset_3a9328bc-9c1c-4498-be8f-bda3883276f5", base_url="...", api_key="...")

Quick Start

Create a New Presentation

from pptx import Presentation
from pptx.util import Inches

# Create a new empty presentation
prs = Presentation.create(name="My Presentation")

# Add a slide
slide = prs.slides.add_slide()

# Add a textbox
tb = slide.shapes.add_textbox(
    Inches(1), Inches(1),   # position (left, top)
    Inches(8), Inches(1)    # size (width, height)
)
tb.text_frame.text = "Hello from Python!"

# Save to local file
prs.save("output.pptx")

Upload and Edit an Existing File

from pptx import Presentation

# Upload a local PPTX file
prs = Presentation.upload("existing.pptx")

# Edit the first slide
slide = prs.slides[0]
slide.shapes[0].text_frame.text = "Updated Title"

# Save changes
prs.save("modified.pptx")

Connect to an Existing Presentation

from pptx import Presentation

# Connect to a presentation by its Athena asset id
prs = Presentation(asset_id="asset_3a9328bc-9c1c-4498-be8f-bda3883276f5")

# Work with slides
for slide in prs.slides:
    print(f"Slide {slide.slide_index}: {len(slide.shapes)} shapes")

deck_id= is accepted as a legacy alias for asset_id= (it is the same identifier string); prefer asset_id= in new code.

Important: When working with an existing presentation, you MUST include the asset_id parameter:

prs = Presentation(asset_id="asset_d822b6e3-0a73-4214-9e71-8f28a3f7c9d9")

Without asset_id, the SDK cannot connect to PPTX Studio and will fail.


API Reference

Presentation

The main entry point for working with a presentation.

Class Methods

# Create a new empty presentation
prs = Presentation.create(name="My Presentation")

# Upload a local PPTX file
prs = Presentation.upload("path/to/file.pptx", name="Custom Name")
prs = Presentation.open("path/to/file.pptx")  # Alias for upload()

# Connect from a full URL
prs = Presentation.from_url("https://api.example.com/decks/deck_123")

# Connect to an existing presentation by asset id
prs = Presentation(asset_id="asset_3a9328bc-9c1c-4498-be8f-bda3883276f5")

Properties

prs.asset_id        # str: Athena asset id (prs.deck_id is the legacy alias)
prs.slides          # Slides: Collection of slides
prs.slide_width     # Emu: Width of slides
prs.slide_height    # Emu: Height of slides
prs.slide_layouts   # SlideLayouts (not yet supported)
prs.slide_masters   # SlideMasters (not yet supported)

Methods

prs.refresh()                     # Sync with server
prs.save("output.pptx")           # Export and download
prs.save_to_bytes()               # Export as bytes
prs.render_slide(index, scale=2)  # Render slide as PNG
prs.reorder_slides([2, 0, 1])     # Reorder slides
prs.get_connection_info()         # Get WebSocket info for real-time collab

Slides

Collection of slides in the presentation.

# Access slides
slide = prs.slides[0]           # By index
for slide in prs.slides:        # Iterate
    print(slide.slide_id)
len(prs.slides)                 # Count

# Add and delete slides
new_slide = prs.slides.add_slide()
prs.slides.delete(slide)

# Athena Extensions
prs.slides.delete_slides([1, 3, 5])       # Delete multiple at once
prs.slides.keep_only([0, 2])              # Keep only specified slides

Slide

A single slide in the presentation.

Properties

slide.slide_id          # str: Unique identifier
slide.slide_index       # int: Zero-based position
slide.shapes            # Shapes: Collection of shapes
slide.placeholders      # SlidePlaceholders: Placeholder shapes
slide.background        # SlideBackground: Background formatting
slide.notes             # str | None: Speaker notes
slide.has_notes_slide   # bool: Whether slide has notes
slide.notes_slide       # NotesSlide: notes_text_frame adapter

Methods

# Athena Extensions
slide.render(scale=2, as_pil=False)   # Render to PNG or PIL Image
slide.clone(target_index=None)         # Clone slide with all content

Setting Notes

slide.notes = "These are my speaker notes"

# python-pptx compatible notes adapter
slide.notes_slide.notes_text_frame.text = "These are my speaker notes"

Setting Background

slide.background.color = 'FFFFFF'              # White
slide.background.fill.fore_color.rgb = RGBColor(0, 0, 255)  # Blue
slide.background.follow_master_background()    # Reset to master

Shapes

Collection of shapes on a slide.

# Access shapes
shape = slide.shapes[0]
for shape in slide.shapes:
    print(shape.shape_type)
len(slide.shapes)

# Get shape by ID
shape = slide.shapes.get_by_id("shp_abc123")

# Title placeholder (if present)
title = slide.shapes.title
if title:
    title.text = "New Title"

Adding Shapes

from pptx.util import Inches, Pt
from pptx.enum.shapes import MSO_SHAPE

# Add textbox
tb = slide.shapes.add_textbox(
    Inches(1), Inches(1),   # left, top
    Inches(6), Inches(1)    # width, height
)

# Add autoshape
shape = slide.shapes.add_shape(
    MSO_SHAPE.ROUNDED_RECTANGLE,
    Inches(1), Inches(2),
    Inches(3), Inches(2)
)

# Add picture
pic = slide.shapes.add_picture(
    "image.png",            # Path, BytesIO, or bytes
    Inches(1), Inches(3),
    width=Inches(4)         # Optional size
)

# Add table
table = slide.shapes.add_table(
    rows=3, cols=4,
    left=Inches(1), top=Inches(4),
    width=Inches(8), height=Inches(2)
)

Shape

Individual shape on a slide.

Properties

shape.shape_id          # str: Unique identifier
shape.shape_type        # str: "text", "image", "shape", "table"
shape.auto_shape_type   # str | None: e.g., "rectangle", "oval"
shape.source            # str | None: "ingested" or "sdk"

# Position and size (EMU)
shape.left              # X position
shape.top               # Y position
shape.width             # Width
shape.height            # Height
shape.rotation          # Degrees
shape.flip_h            # Horizontal flip
shape.flip_v            # Vertical flip

# Text (for text boxes and auto-shapes)
shape.has_text_frame    # bool
shape.text_frame        # TextFrame
shape.text              # str (shortcut for text_frame.text)

# Placeholders
shape.is_placeholder    # bool
shape.placeholder_format  # PlaceholderFormat | None

# Styling
shape.fill              # FillFormat
shape.line              # LineFormat

Methods

shape.delete()          # Remove shape from slide

Positioning

from pptx.util import Inches

shape.left = Inches(2)
shape.top = Inches(3)
shape.width = Inches(4)
shape.height = Inches(2)
shape.rotation = 45     # degrees

TextFrame

Container for text content in a shape.

tf = shape.text_frame

# Get/set all text
tf.text = "Hello World"
print(tf.text)

# Work with paragraphs
for para in tf.paragraphs:
    print(para.text)

# Add paragraph
para = tf.add_paragraph()
para.text = "New paragraph"

# Clear all text
tf.clear()

Paragraph

A paragraph containing text runs with consistent styling.

para = tf.paragraphs[0]

# Text content
para.text = "Paragraph text"

# Alignment
para.alignment = 'center'  # 'left', 'center', 'right', 'justify'

# Indentation level (for bullets)
para.level = 1  # 0-8

# Spacing
para.line_spacing = 1.5       # Line spacing multiplier
para.space_before = Pt(12)    # Space before (EMU)
para.space_after = Pt(6)      # Space after (EMU)

# Margins
para.margin_left = Inches(0.5)
para.indent = Inches(-0.25)   # Hanging indent (negative)

# Font (applies to first run)
para.font.bold = True
para.font.size = Pt(14)

Run

A run of text with consistent formatting within a paragraph.

run = para.runs[0]

# Text
run.text = "Styled text"

# Add new run
new_run = para.add_run()
new_run.text = " more text"

# Font styling
run.font.bold = True
run.font.italic = True
run.font.underline = True
run.font.name = "Arial"
run.font.size = Pt(14)
run.font.color.rgb = RGBColor(255, 0, 0)  # Red
run.font.spacing = 1.5  # Character spacing in points

Font

Font formatting for text runs.

font = run.font

# Properties
font.bold = True
font.italic = True
font.underline = True
font.name = "Calibri"
font.size = Pt(12)        # In EMU (use Pt() helper)
font.spacing = 2.0        # Character spacing in points

# Color
font.color.rgb = RGBColor(0, 128, 255)

FillFormat

Fill formatting for shapes.

fill = shape.fill

# Set solid color
fill.solid()
fill.fore_color.rgb = RGBColor(255, 0, 0)  # Red

# Transparency (0.0 = opaque, 1.0 = transparent)
fill.transparency = 0.5

# Remove fill
fill.background()

LineFormat

Line (outline/border) formatting for shapes.

line = shape.line

# Color
line.color.rgb = RGBColor(0, 0, 0)  # Black

# Width
line.width = Pt(2)  # 2-point line

# Dash style
line.dash_style = 'dash'  # 'solid', 'dash', 'dot', 'dash_dot', 'long_dash'

# Remove line
line.no_fill()

Table

A table shape with rows and columns.

# Add table
table = slide.shapes.add_table(3, 4, Inches(1), Inches(1), Inches(8), Inches(3))

# Access cells
cell = table.cell(row=0, col=0)
cell.text = "Header"
cell.fill = 'CCCCCC'  # Gray background

# Dimensions
print(table.rows, table.cols)

# Iterate cells
for cell in table.iter_cells():
    print(cell.text)

Placeholders

Access placeholder shapes on a slide.

# Access by idx
title = slide.placeholders[0]        # Title (idx 0)
body = slide.placeholders[1]         # Body (idx 1)

# Check existence
if 0 in slide.placeholders:
    slide.placeholders[0].text = "Title"

# Iterate
for idx, placeholder in slide.placeholders.items():
    print(f"idx {idx}: {placeholder.placeholder_format.type}")

# Placeholder format
ph = shape.placeholder_format
print(ph.type)              # PP_PLACEHOLDER enum
print(ph.idx)               # Integer index
print(ph.has_custom_prompt) # Whether has custom prompt text

Units

Unit conversion helpers (python-pptx compatible).

from pptx.util import Inches, Pt, Cm, Mm, Px, Emu, Centipoints

# All return Emu (English Metric Units)
Inches(1)       # 914400 EMU
Pt(72)          # 914400 EMU (72 points = 1 inch)
Cm(2.54)        # 914400 EMU (~1 inch)
Mm(25.4)        # 914400 EMU (1 inch)
Px(96)          # 914400 EMU (at 96 DPI)
Emu(914400)     # Direct EMU value
Centipoints(7200)  # 914400 EMU

# Emu objects have conversion properties
length = Inches(2)
length.inches      # 2.0
length.cm          # 5.08
length.pt          # 144.0
length.px          # 192.0 (at 96 DPI)
length.emu         # 1828800

RGBColor

Color representation (python-pptx compatible).

from pptx.dml.color import RGBColor

# Create color
red = RGBColor(255, 0, 0)
blue = RGBColor(0x00, 0x00, 0xFF)

# From hex string
green = RGBColor.from_string("00FF00")

# Properties
print(red.red)    # 255
print(red.green)  # 0
print(red.blue)   # 0
print(str(red))   # "FF0000"

Shape Types (MSO_SHAPE)

AutoShape type enumeration.

from pptx.enum.shapes import MSO_SHAPE

# Basic shapes
MSO_SHAPE.RECTANGLE
MSO_SHAPE.ROUNDED_RECTANGLE
MSO_SHAPE.OVAL
MSO_SHAPE.TRIANGLE
MSO_SHAPE.DIAMOND

# Stars
MSO_SHAPE.STAR_5_POINT
MSO_SHAPE.STAR_6_POINT

# Arrows
MSO_SHAPE.RIGHT_ARROW
MSO_SHAPE.LEFT_ARROW
MSO_SHAPE.CHEVRON

# Flowchart
MSO_SHAPE.FLOWCHART_PROCESS
MSO_SHAPE.FLOWCHART_DECISION

# And many more...

Batching Operations

For better performance, batch multiple operations into a single API request:

with prs.batch():
    # All operations collected and sent as one request
    slide = prs.slides.add_slide()
    tb1 = slide.shapes.add_textbox(Inches(1), Inches(1), Inches(4), Inches(1))
    tb1.text_frame.text = "First"
    tb2 = slide.shapes.add_textbox(Inches(1), Inches(2), Inches(4), Inches(1))
    tb2.text_frame.text = "Second"
# Request sent here when context exits

Error Handling

The SDK raises specific exceptions for different error conditions:

from pptx import (
    PptxSdkError,           # Base exception
    UnsupportedFeatureError,  # Feature not yet implemented
    RemoteError,              # Server rejected the request
    ConflictError,            # Stale IDs (concurrent modification)
    ValidationError,          # Invalid command parameters
    ConnectionError,          # Network issues
    AuthenticationError,      # Auth failed
    ExportError,              # Export job failed
    RenderError,              # Render job failed
    UploadError,              # Upload failed
)

# Handling unsupported features
try:
    prs.core_properties
except UnsupportedFeatureError as e:
    print(f"Not yet supported: {e.feature}")

# Handling conflicts
try:
    shape.text = "New text"
except ConflictError as e:
    print(f"Stale IDs: {e.stale_ids}")
    prs.refresh()  # Sync with server

Athena Extensions

These features are exclusive to athena-python-pptx and not available in standard python-pptx:

Slide Rendering

# Render slide to PNG bytes
png_bytes = slide.render(scale=2)
with open("slide.png", "wb") as f:
    f.write(png_bytes)

# Render to PIL Image (requires Pillow)
img = slide.render(as_pil=True)
img.show()

Slide Cloning

# Clone slide and insert after it
new_slide = slide.clone()

# Clone and insert at specific position
new_slide = slide.clone(target_index=0)

Bulk Slide Operations

# Delete multiple slides at once
prs.slides.delete_slides([1, 3, 5])

# Keep only specified slides
prs.slides.keep_only([0, 2, 4])

Real-time Collaboration Info

# Get WebSocket connection info for real-time sync
info = prs.get_connection_info()
print(info['wsUrl'])
print(info['authToken'])

Supported Features

Fully Supported

  • Create, upload, and export presentations
  • Add, delete, and reorder slides
  • Add textboxes, shapes, pictures, tables, and charts (all category / XY / bubble types)
  • Set text content and formatting (bold, italic, underline, size, color, language_id, strike, sub/superscript)
  • Set paragraph alignment, spacing, and indentation
  • Set shape position, size, rotation, and flip
  • Set shape fill (solid / gradient / pattern), line styling, and shadow
  • Access placeholders + slide layouts and masters (including layout fill / background / placeholder cloning by index)
  • Hyperlinks and click actions (PP_ACTION_TYPE: HYPERLINK, NEXT_SLIDE, PREVIOUS_SLIDE, etc.)
  • Notes slides — read/write notes text via slide.notes_slide.notes_text_frame
  • Render slides to PNG
  • Clone slides
  • Batch operations
  • All upstream chart enums (XL_CHART_TYPE, XL_LEGEND_POSITION, XL_DATA_LABEL_POSITION, XL_TICK_MARK, XL_TICK_LABEL_POSITION, XL_AXIS_CROSSES, XL_CATEGORY_TYPE, XL_MARKER_STYLE)

Documented Departures (REST-SDK specific)

See docs/API_PARITY_EXCEPTIONS.md for the full list. Highlights:

  • Presentation(filename) not supported — use Presentation.upload(path) or Presentation(asset_id=…) instead (deck_id= is the legacy alias).
  • NotesSlide.shapes / .placeholders / .notes_placeholder not yet exposed — use slide.notes_slide.notes_text_frame for text.
  • XyChartData.add_series(name, values) signature drift vs upstream's add_series(name, number_format=None).
  • TextFitter.best_fit_font_size() returns max_size unchanged (auto-fit is server-side).
  • Package.open() raises UnsupportedFeatureError.

Unsupported features raise UnsupportedFeatureError with a clear message.


Development

# Clone and install dev dependencies
git clone https://github.com/pptx-studio/python-sdk
cd python-sdk
pip install -e ".[dev]"

# Run unit tests
pytest tests/ -v --ignore=tests/test_smoke_integration.py

# Type checking
mypy pptx

# Linting
ruff check pptx

Running Integration Tests

Integration tests require a running PPTX Studio server.

# Start the PPTX Studio server (from project root)
pnpm dev:infra
pnpm dev

# Run integration tests
ATHENA_PPTX_BASE_URL=http://localhost:4000 pytest tests/test_smoke_integration.py -v

Generate Documentation

# Generate Athena-specific API docs (Markdown)
athena-pptx-docs > docs/athena-api.md

# Generate as JSON
athena-pptx-docs --format json > docs/athena-api.json

License

MIT

Project details


Release history Release notifications | RSS feed

Download files

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

Source Distribution

athena_python_pptx-0.4.3.tar.gz (411.2 kB view details)

Uploaded Source

Built Distribution

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

athena_python_pptx-0.4.3-py3-none-any.whl (290.3 kB view details)

Uploaded Python 3

File details

Details for the file athena_python_pptx-0.4.3.tar.gz.

File metadata

  • Download URL: athena_python_pptx-0.4.3.tar.gz
  • Upload date:
  • Size: 411.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.6

File hashes

Hashes for athena_python_pptx-0.4.3.tar.gz
Algorithm Hash digest
SHA256 d16e79c0682bfc06c0bae28a75ea01dcc3f2738e3f4c4f68d69efabbba7d2c1b
MD5 543154f774e287d75fd3bfd74b108112
BLAKE2b-256 2da18d697af0f01b427bc231e0e7b781f9be080edc1198413cdbf7b268c791bd

See more details on using hashes here.

File details

Details for the file athena_python_pptx-0.4.3-py3-none-any.whl.

File metadata

File hashes

Hashes for athena_python_pptx-0.4.3-py3-none-any.whl
Algorithm Hash digest
SHA256 044b4c1be11968a53e9a559d677bf3554ccedb6ac86d1a45f22862f64c26b73e
MD5 d18e0fd6b66089899ba09d9d96c55601
BLAKE2b-256 e604f57c7d58040685ac5d81c8a7561d4cfbf70a343636a1269adc5e3f2178eb

See more details on using hashes here.

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