This directory contains the enhanced python-pptx library with the bullet_style property for easy bullet and numbered list creation.
from pptx import Presentation
from pptx.util import Pt, Inches
# Create presentation
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6])
# Add text box
textbox = slide.shapes.add_textbox(Inches(1), Inches(1), Inches(8), Inches(5))
tf = textbox.text_frame
# Title (no bullet)
p = tf.paragraphs[0]
p.text = "My List"
p.font.size = Pt(24)
p.font.bold = True
# Bullet points
p1 = tf.add_paragraph()
p1.text = "First bullet item"
p1.bullet_style = "bullet"
p1.font.size = Pt(18)
# Indented bullet (level 1)
p2 = tf.add_paragraph()
p2.text = "Sub-item"
p2.level = 1
p2.bullet_style = "bullet"
p2.font.size = Pt(16)
# Numbered list
p3 = tf.add_paragraph()
p3.text = "First numbered item"
p3.bullet_style = "number"
p3.font.size = Pt(18)
prs.save("output.pptx")Read/write property that controls bullet formatting.
| Value | Result |
|---|---|
"bullet" |
Character bullet (•) |
"number" |
Auto-numbered list (1., 2., 3.) |
None or "" |
No bullet (plain text) |
Read/write property (0-8) that controls indentation level. Works with bullet_style for nested lists.
Important: Set level before bullet_style for proper indentation calculation.
p.level = 0 # Main level (0.125" indent)
p.level = 1 # First sub-level (0.25" indent)
p.level = 2 # Second sub-level (0.375" indent)The bullet_style property correctly reads existing bullets from PPTX files:
from pptx import Presentation
prs = Presentation("existing_file.pptx")
for slide in prs.slides:
for shape in slide.shapes:
if shape.has_text_frame:
for p in shape.text_frame.paragraphs:
print(f"Text: {p.text}")
print(f"Bullet: {p.bullet_style}") # Returns "bullet", "number", or NoneComprehensive test suite with 4 tests:
python src/test_roundtrip_bullets.pyTests:
- Read existing bullets - Opens PPTX with bullets, verifies
bullet_stylereturns correct values - Save and reload - Round-trip test ensuring bullets survive save/load
- Modify existing - Adds new bullets to existing presentation
- Create from scratch - Creates new presentation with mixed bullet types
Tests bullet functionality on a complex real presentation:
python src/test_bullet_on_existing_pptx.pyShows the underlying XML structure for debugging:
python src/inspect_bullets.pyOutput shows:
- Paragraph bullet_style values
- Underlying XML elements (
buChar,buAutoNum) - Raw XML for paragraphs with bullets
Simple demonstration of all bullet_style capabilities:
python src/demo_bullet_style.pyPytest unit tests for the bullet_style property:
# Windows PowerShell
$env:PYTHONPATH="src"; pytest tests/text/test_bullet_style.py -v
# Linux/Mac
PYTHONPATH=src pytest tests/text/test_bullet_style.py -v
# Run specific test class
$env:PYTHONPATH="src"; pytest tests/text/test_bullet_style.py::DescribeParagraphBulletStyle -v
# Run integration tests only
$env:PYTHONPATH="src"; pytest tests/text/test_bullet_style.py::DescribeIntegrationBulletStyle -vUnit Tests (DescribeParagraphBulletStyle):
- Returns
Nonewhen no bullet element present - Returns
NonewhenpPrnot present - Returns
"bullet"whenbuCharelement present - Returns
"number"whenbuAutoNumelement present - Can set bullet_style to
"bullet"or"number" - Can remove bullet by setting to
Noneor"" - Can change between bullet types
- Raises
ValueErrorfor invalid values
Integration Tests (DescribeIntegrationBulletStyle):
- Works with real
Presentationobjects
All test scripts output to src/demo_test_results/:
| File | Description |
|---|---|
PresentationWBullets_roundtrip.pptx |
Exact copy (round-trip verification) |
PresentationWBullets_modified.pptx |
Original + new bullets added |
PresentationWBullets_created.pptx |
Created from scratch |
image_exercise_with_bullets.pptx |
Modified real-world template |
bullet_style_demo.pptx |
Feature demonstration |
# Plain text header
p = tf.paragraphs[0]
p.text = "Project Status"
p.font.bold = True
# Bullet points
for item in ["Completed design", "In progress: development", "Pending: testing"]:
p = tf.add_paragraph()
p.text = item
p.bullet_style = "bullet"
p.font.size = Pt(14)
# Numbered action items
for i, action in enumerate(["Review code", "Fix bugs", "Deploy"]):
p = tf.add_paragraph()
p.text = action
p.bullet_style = "number"
p.font.size = Pt(14)items = [
("Main topic 1", 0),
("Sub-point A", 1),
("Sub-point B", 1),
("Detail under B", 2),
("Main topic 2", 0),
]
for text, level in items:
p = tf.add_paragraph()
p.text = text
p.level = level # Set level BEFORE bullet_style
p.bullet_style = "bullet"
p.font.size = Pt(18 - level * 2) # Smaller font for sub-itemsfor p in tf.paragraphs:
if p.bullet_style is not None:
p.bullet_style = None # Removes bullet formattingThe bullet_style property maps to OpenXML elements:
| Property Value | XML Element |
|---|---|
"bullet" |
<a:buChar char="•"/> |
"number" |
<a:buAutoNum type="arabicPeriod"/> |
None |
<a:buNone/> or no element |
When reading existing files, the getter checks which element exists:
@property
def bullet_style(self):
if pPr.buChar is not None:
return "bullet"
if pPr.buAutoNum is not None:
return "number"
return Nonesrc/
├── pptx/
│ ├── text/
│ │ └── text.py # _Paragraph.bullet_style property
│ └── oxml/
│ ├── __init__.py # Element class registration
│ └── text.py # CT_TextCharBullet, CT_TextAutonumberBullet
├── demo_test_results/ # Output directory for test files
├── test_roundtrip_bullets.py
├── test_bullet_on_existing_pptx.py
├── inspect_bullets.py
├── demo_bullet_style.py
├── PresentationWBullets.pptx # Test input file
└── image_exercise.pptx # Complex test input
python >= 3.9
lxml
Pillow
Install dependencies:
pip install lxml Pillow- Bullet character is fixed to
•(U+2022) - Numbering type is fixed to
arabicPeriod(1., 2., 3.) - Bullet font and color are not configurable through this API
- Start number for numbered lists is not exposed
- Bullets inherit paragraph alignment from template - use left-aligned text for proper nested indentation
See IMPLEMENTATION.md for technical details and future enhancement plans.