diff --git a/docs/index.rst b/docs/index.rst
index 5bb4e0e26..79ad6c369 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -11,7 +11,7 @@ Philosophy
----------
|pp| aims to broadly support the PowerPoint format (PPTX, PowerPoint 2007 and later),
-but its primary commitment is to be _industrial-grade_, that is, suitable for use in a
+but its primary commitment is to be *industrial-grade*, that is, suitable for use in a
commercial setting. Maintaining this robustness requires a high engineering standard
which includes a comprehensive two-level (e2e + unit) testing regimen. This discipline
comes at a cost in development effort/time, but we consider reliability to be an
diff --git a/features/steps/action.py b/features/steps/action.py
index c3f5de0e2..2a2da135a 100644
--- a/features/steps/action.py
+++ b/features/steps/action.py
@@ -1,16 +1,14 @@
-# encoding: utf-8
-
"""Gherkin step implementations for click action-related features."""
+from __future__ import annotations
+
from behave import given, then, when
+from helpers import test_file
from pptx import Presentation
from pptx.action import Hyperlink
from pptx.enum.action import PP_ACTION
-from helpers import test_file
-
-
# given ===================================================
diff --git a/features/steps/axis.py b/features/steps/axis.py
index 9cffbb93a..59697461b 100644
--- a/features/steps/axis.py
+++ b/features/steps/axis.py
@@ -1,17 +1,13 @@
-# encoding: utf-8
-
"""Gherkin step implementations for chart axis features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
from pptx.enum.chart import XL_AXIS_CROSSES, XL_CATEGORY_TYPE
-from helpers import test_pptx
-
-
# given ===================================================
@@ -19,9 +15,7 @@
def given_a_axis_type_axis(context, axis_type):
prs = Presentation(test_pptx("cht-axis-props"))
chart = prs.slides[0].shapes[0].chart
- context.axis = {"category": chart.category_axis, "value": chart.value_axis}[
- axis_type
- ]
+ context.axis = {"category": chart.category_axis, "value": chart.value_axis}[axis_type]
@given("a major gridlines")
@@ -33,9 +27,7 @@ def given_a_major_gridlines(context):
@given("a value axis having category axis crossing of {crossing}")
def given_a_value_axis_having_cat_ax_crossing_of(context, crossing):
- slide_idx = {"automatic": 0, "maximum": 2, "minimum": 3, "2.75": 4, "-1.5": 5}[
- crossing
- ]
+ slide_idx = {"automatic": 0, "maximum": 2, "minimum": 3, "2.75": 4, "-1.5": 5}[crossing]
prs = Presentation(test_pptx("cht-axis-props"))
context.value_axis = prs.slides[slide_idx].shapes[0].chart.value_axis
@@ -122,9 +114,7 @@ def when_I_assign_value_to_axis_has_title(context, value):
@when("I assign {value} to axis.has_{major_or_minor}_gridlines")
-def when_I_assign_value_to_axis_has_major_or_minor_gridlines(
- context, value, major_or_minor
-):
+def when_I_assign_value_to_axis_has_major_or_minor_gridlines(context, value, major_or_minor):
axis = context.axis
propname = "has_%s_gridlines" % major_or_minor
new_value = {"True": True, "False": False}[value]
@@ -210,9 +200,7 @@ def then_axis_has_title_is_value(context, value):
@then("axis.has_{major_or_minor}_gridlines is {value}")
-def then_axis_has_major_or_minor_gridlines_is_expected_value(
- context, major_or_minor, value
-):
+def then_axis_has_major_or_minor_gridlines_is_expected_value(context, major_or_minor, value):
axis = context.axis
actual_value = {
"major": axis.has_major_gridlines,
@@ -233,9 +221,7 @@ def then_axis_major_or_minor_unit_is_value(context, major_or_minor, value):
axis = context.axis
propname = "%s_unit" % major_or_minor
actual_value = getattr(axis, propname)
- expected_value = {"20.0": 20.0, "8.4": 8.4, "5.0": 5.0, "4.2": 4.2, "None": None}[
- value
- ]
+ expected_value = {"20.0": 20.0, "8.4": 8.4, "5.0": 5.0, "4.2": 4.2, "None": None}[value]
assert actual_value == expected_value, "got %s" % actual_value
diff --git a/features/steps/background.py b/features/steps/background.py
index 596a3a665..b629cea74 100644
--- a/features/steps/background.py
+++ b/features/steps/background.py
@@ -1,15 +1,11 @@
-# encoding: utf-8
-
"""Gherkin step implementations for slide background-related features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then
-
-from pptx import Presentation
-
from helpers import test_pptx
+from pptx import Presentation
# given ===================================================
diff --git a/features/steps/category.py b/features/steps/category.py
index 3a119f960..2c4a10ce3 100644
--- a/features/steps/category.py
+++ b/features/steps/category.py
@@ -1,15 +1,11 @@
-# encoding: utf-8
-
"""Gherkin step implementations for chart category features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then
-
-from pptx import Presentation
-
from helpers import test_pptx
+from pptx import Presentation
# given ===================================================
diff --git a/features/steps/chart.py b/features/steps/chart.py
index fd4edefc2..ced211f32 100644
--- a/features/steps/chart.py
+++ b/features/steps/chart.py
@@ -1,16 +1,12 @@
-# encoding: utf-8
+"""Gherkin step implementations for chart features."""
-"""
-Gherkin step implementations for chart features.
-"""
-
-from __future__ import absolute_import, print_function
+from __future__ import annotations
import hashlib
-
from itertools import islice
from behave import given, then, when
+from helpers import count, test_pptx
from pptx import Presentation
from pptx.chart.chart import Legend
@@ -19,9 +15,6 @@
from pptx.parts.embeddedpackage import EmbeddedXlsxPart
from pptx.util import Inches
-from helpers import count, test_pptx
-
-
# given ===================================================
diff --git a/features/steps/chartdata.py b/features/steps/chartdata.py
index c116a0cb3..82e88ff5a 100644
--- a/features/steps/chartdata.py
+++ b/features/steps/chartdata.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
-
"""Gherkin step implementations for chart data features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import datetime
@@ -12,7 +10,6 @@
from pptx.enum.chart import XL_CHART_TYPE
from pptx.util import Inches
-
# given ===================================================
diff --git a/features/steps/color.py b/features/steps/color.py
index 590cabf79..43bb3cc08 100644
--- a/features/steps/color.py
+++ b/features/steps/color.py
@@ -1,18 +1,14 @@
-# encoding: utf-8
-
"""Gherkin step implementations for ColorFormat-related features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
-from behave import given, when, then
+from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.enum.dml import MSO_THEME_COLOR
-from helpers import test_pptx
-
-
# given ====================================================
diff --git a/features/steps/coreprops.py b/features/steps/coreprops.py
index bda998b71..9989c2e01 100644
--- a/features/steps/coreprops.py
+++ b/features/steps/coreprops.py
@@ -1,20 +1,14 @@
-# encoding: utf-8
+"""Gherkin step implementations for core properties-related features."""
-"""
-Gherkin step implementations for core properties-related features.
-"""
-
-from __future__ import absolute_import
+from __future__ import annotations
from datetime import datetime, timedelta
-from behave import given, when, then
+from behave import given, then, when
+from helpers import no_core_props_pptx_path, saved_pptx_path
from pptx import Presentation
-from helpers import saved_pptx_path, no_core_props_pptx_path
-
-
# given ===================================================
@@ -49,7 +43,7 @@ def step_when_set_core_doc_props_to_valid_values(context):
("revision", 9),
("subject", "Subject"),
# --- exercise unicode-text case for Python 2.7 ---
- ("title", u"åß∂Title°"),
+ ("title", "åß∂Title°"),
("version", "Version"),
)
for name, value in context.propvals:
diff --git a/features/steps/datalabel.py b/features/steps/datalabel.py
index dc56de4e4..bb4f474aa 100644
--- a/features/steps/datalabel.py
+++ b/features/steps/datalabel.py
@@ -1,17 +1,13 @@
-# encoding: utf-8
-
"""Gherkin step implementations for chart data label features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
from pptx.enum.chart import XL_DATA_LABEL_POSITION
-from helpers import test_pptx
-
-
# given ===================================================
diff --git a/features/steps/effect.py b/features/steps/effect.py
index f319545fc..c9e2806cc 100644
--- a/features/steps/effect.py
+++ b/features/steps/effect.py
@@ -1,15 +1,11 @@
-# encoding: utf-8
-
"""Gherkin step implementations for ShadowFormat-related features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then, when
-
-from pptx import Presentation
-
from helpers import test_pptx
+from pptx import Presentation
# given ====================================================
diff --git a/features/steps/fill.py b/features/steps/fill.py
index fea93ec8f..cbdad36a1 100644
--- a/features/steps/fill.py
+++ b/features/steps/fill.py
@@ -1,17 +1,13 @@
-# encoding: utf-8
-
"""Gherkin step implementations for FillFormat-related features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
from pptx.enum.dml import MSO_FILL, MSO_PATTERN # noqa
-from helpers import test_pptx
-
-
# given ====================================================
@@ -23,9 +19,7 @@ def given_a_FillFormat_object_as_fill(context):
@given("a FillFormat object as fill having {pattern} fill")
def given_a_FillFormat_object_as_fill_having_pattern(context, pattern):
- shape_idx = {"no pattern": 0, "MSO_PATTERN.DIVOT": 1, "MSO_PATTERN.WAVE": 2}[
- pattern
- ]
+ shape_idx = {"no pattern": 0, "MSO_PATTERN.DIVOT": 1, "MSO_PATTERN.WAVE": 2}[pattern]
slide = Presentation(test_pptx("dml-fill")).slides[1]
fill = slide.shapes[shape_idx].fill
context.fill = fill
@@ -102,18 +96,14 @@ def when_I_call_fill_solid(context):
def then_fill_back_color_is_a_ColorFormat_object(context):
actual_value = context.fill.back_color.__class__.__name__
expected_value = "ColorFormat"
- assert actual_value == expected_value, (
- "fill.back_color is a %s object" % actual_value
- )
+ assert actual_value == expected_value, "fill.back_color is a %s object" % actual_value
@then("fill.fore_color is a ColorFormat object")
def then_fill_fore_color_is_a_ColorFormat_object(context):
actual_value = context.fill.fore_color.__class__.__name__
expected_value = "ColorFormat"
- assert actual_value == expected_value, (
- "fill.fore_color is a %s object" % actual_value
- )
+ assert actual_value == expected_value, "fill.fore_color is a %s object" % actual_value
@then("fill.gradient_angle == {value}")
@@ -127,9 +117,7 @@ def then_fill_gradient_angle_eq_value(context, value):
def then_fill_gradient_stops_is_a_GradientStops_object(context):
expected_value = "_GradientStops"
actual_value = context.fill.gradient_stops.__class__.__name__
- assert actual_value == expected_value, (
- "fill.gradient_stops is a %s object" % actual_value
- )
+ assert actual_value == expected_value, "fill.gradient_stops is a %s object" % actual_value
@then("fill.pattern is {value}")
diff --git a/features/steps/font.py b/features/steps/font.py
index 2a4c279c5..a9ea45c6b 100644
--- a/features/steps/font.py
+++ b/features/steps/font.py
@@ -1,20 +1,14 @@
-# encoding: utf-8
+"""Step implementations for run property (font)-related features."""
-"""
-Step implementations for run property (font)-related features
-"""
-
-from __future__ import absolute_import
+from __future__ import annotations
from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
from pptx.enum.lang import MSO_LANGUAGE_ID
from pptx.enum.text import MSO_UNDERLINE
-from helpers import test_pptx
-
-
# given ===================================================
diff --git a/features/steps/font_color.py b/features/steps/font_color.py
index e336978af..53872dff1 100644
--- a/features/steps/font_color.py
+++ b/features/steps/font_color.py
@@ -1,20 +1,14 @@
-# encoding: utf-8
+"""Gherkin step implementations for font color features."""
-"""
-Gherkin step implementations for font color features
-"""
-
-from __future__ import absolute_import, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.enum.dml import MSO_COLOR_TYPE, MSO_THEME_COLOR
-from helpers import test_pptx
-
-
font_color_pptx_path = test_pptx("font-color")
@@ -31,9 +25,7 @@ def step_given_font_with_color_type(context, color_type):
@given("a font with a color brightness setting of {setting}")
def step_font_with_color_brightness(context, setting):
- textbox_idx = {"no brightness adjustment": 2, "25% darker": 3, "40% lighter": 4}[
- setting
- ]
+ textbox_idx = {"no brightness adjustment": 2, "25% darker": 3, "40% lighter": 4}[setting]
context.prs = Presentation(font_color_pptx_path)
textbox = context.prs.slides[0].shapes[textbox_idx]
context.font = textbox.text_frame.paragraphs[0].runs[0].font
diff --git a/features/steps/helpers.py b/features/steps/helpers.py
index bd6d7a330..67a29439a 100644
--- a/features/steps/helpers.py
+++ b/features/steps/helpers.py
@@ -1,13 +1,9 @@
-# encoding: utf-8
-
-"""
-Helper methods and variables for acceptance tests.
-"""
+"""Helper methods and variables for acceptance tests."""
import os
-def absjoin(*paths):
+def absjoin(*paths: str) -> str:
return os.path.abspath(os.path.join(*paths))
@@ -17,9 +13,7 @@ def absjoin(*paths):
test_pptx_dir = absjoin(thisdir, "test_files")
# legacy test pptx files ---------------
-no_core_props_pptx_path = absjoin(
- thisdir, "../../tests/test_files", "no-core-props.pptx"
-)
+no_core_props_pptx_path = absjoin(thisdir, "../../tests/test_files", "no-core-props.pptx")
# scratch test pptx file ---------------
saved_pptx_path = absjoin(scratch_dir, "test_out.pptx")
@@ -27,41 +21,31 @@ def absjoin(*paths):
test_text = "python-pptx was here!"
-def cls_qname(obj):
+def cls_qname(obj: object) -> str:
module_name = obj.__module__
cls_name = obj.__class__.__name__
qname = "%s.%s" % (module_name, cls_name)
return qname
-def count(start=0, step=1):
- """
- Local implementation of `itertools.count()` to allow v2.6 compatibility.
- """
+def count(start: int = 0, step: int = 1):
+ """Local implementation of `itertools.count()` to allow v2.6 compatibility."""
n = start
while True:
yield n
n += step
-def test_file(filename):
- """
- Return the absolute path to the file having *filename* in acceptance
- test_files directory.
- """
+def test_file(filename: str) -> str:
+ """Return the absolute path to the file having *filename* in acceptance test_files directory."""
return absjoin(thisdir, "test_files", filename)
-def test_image(filename):
- """
- Return the absolute path to image file having *filename* in test_files
- directory.
- """
+def test_image(filename: str):
+ """Return the absolute path to image file having *filename* in test_files directory."""
return absjoin(thisdir, "test_files", filename)
-def test_pptx(name):
- """
- Return the absolute path to test .pptx file with root name *name*.
- """
+def test_pptx(name: str) -> str:
+ """Return the absolute path to test .pptx file with root name *name*."""
return absjoin(thisdir, "test_files", "%s.pptx" % name)
diff --git a/features/steps/legend.py b/features/steps/legend.py
index f8385f12a..7c35cd7f7 100644
--- a/features/steps/legend.py
+++ b/features/steps/legend.py
@@ -1,18 +1,14 @@
-# encoding: utf-8
-
"""Gherkin step implementations for chart legend features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
from pptx.enum.chart import XL_LEGEND_POSITION
from pptx.text.text import Font
-from helpers import test_pptx
-
-
# given ===================================================
@@ -31,9 +27,7 @@ def given_a_legend_having_horizontal_offset_of_value(context, value):
@given("a legend positioned {location} the chart")
def given_a_legend_positioned_location_the_chart(context, location):
- slide_idx = {"at an unspecified location of": 0, "below": 1, "to the right of": 2}[
- location
- ]
+ slide_idx = {"at an unspecified location of": 0, "below": 1, "to the right of": 2}[location]
prs = Presentation(test_pptx("cht-legend-props"))
context.legend = prs.slides[slide_idx].shapes[0].chart.legend
diff --git a/features/steps/line.py b/features/steps/line.py
index 5489b03ed..fb1cb1bb3 100644
--- a/features/steps/line.py
+++ b/features/steps/line.py
@@ -1,18 +1,14 @@
-# encoding: utf-8
-
"""Step implementations for LineFormat-related features."""
-from __future__ import absolute_import, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
from pptx.enum.dml import MSO_LINE
from pptx.util import Length, Pt
-from helpers import test_pptx
-
-
# given ===================================================
diff --git a/features/steps/picture.py b/features/steps/picture.py
index ef6dbbe75..2fce7f2ca 100644
--- a/features/steps/picture.py
+++ b/features/steps/picture.py
@@ -1,18 +1,17 @@
-# encoding: utf-8
-
"""Gherkin step implementations for picture-related features."""
-from behave import given, when, then
+from __future__ import annotations
+
+import io
+
+from behave import given, then, when
+from helpers import saved_pptx_path, test_image, test_pptx
from pptx import Presentation
-from pptx.compat import BytesIO
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
from pptx.package import Package
from pptx.util import Inches
-from helpers import saved_pptx_path, test_image, test_pptx
-
-
# given ===================================================
@@ -42,7 +41,7 @@ def when_I_add_the_image_filename_using_shapes_add_picture(context, filename):
def when_I_add_the_stream_image_filename_using_add_picture(context, filename):
shapes = context.slide.shapes
with open(test_image(filename), "rb") as f:
- stream = BytesIO(f.read())
+ stream = io.BytesIO(f.read())
shapes.add_picture(stream, Inches(1.25), Inches(1.25))
@@ -59,9 +58,7 @@ def step_then_a_ext_image_part_appears_in_the_pptx_file(context, ext):
pkg = Package.open(saved_pptx_path)
partnames = frozenset(p.partname for p in pkg.iter_parts())
image_partname = "/ppt/media/image1.%s" % ext
- assert image_partname in partnames, "got %s" % [
- p for p in partnames if "image" in p
- ]
+ assert image_partname in partnames, "got %s" % [p for p in partnames if "image" in p]
@then("picture.auto_shape_type == MSO_AUTO_SHAPE_TYPE.{member}")
diff --git a/features/steps/placeholder.py b/features/steps/placeholder.py
index 2ea14f492..43638373d 100644
--- a/features/steps/placeholder.py
+++ b/features/steps/placeholder.py
@@ -1,14 +1,11 @@
-# encoding: utf-8
+"""Gherkin step implementations for placeholder-related features."""
-"""
-Gherkin step implementations for placeholder-related features.
-"""
-
-from __future__ import absolute_import
+from __future__ import annotations
import hashlib
-from behave import given, when, then
+from behave import given, then, when
+from helpers import saved_pptx_path, test_file, test_pptx, test_text
from pptx import Presentation
from pptx.chart.data import CategoryChartData
@@ -16,9 +13,6 @@
from pptx.enum.shapes import MSO_SHAPE_TYPE, PP_PLACEHOLDER
from pptx.shapes.base import _PlaceholderFormat
-from helpers import saved_pptx_path, test_file, test_pptx, test_text
-
-
# given ===================================================
@@ -32,9 +26,7 @@ def given_a_bullet_body_placeholder(context):
@given("a known {placeholder_type} placeholder shape")
def given_a_known_placeholder_shape(context, placeholder_type):
- context.execute_steps(
- "given an unpopulated %s placeholder shape" % placeholder_type
- )
+ context.execute_steps("given an unpopulated %s placeholder shape" % placeholder_type)
@given("a layout placeholder having directly set position and size")
diff --git a/features/steps/plot.py b/features/steps/plot.py
index 98feb88e5..0a3636717 100644
--- a/features/steps/plot.py
+++ b/features/steps/plot.py
@@ -1,15 +1,11 @@
-# encoding: utf-8
-
"""Gherkin step implementations for chart plot features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then, when
-
-from pptx import Presentation
-
from helpers import test_pptx
+from pptx import Presentation
# given ===================================================
@@ -95,9 +91,7 @@ def when_I_assign_value_to_plot_vary_by_categories(context, value):
def then_bubble_plot_bubble_scale_is_value(context, value):
expected_value = int(value)
bubble_plot = context.bubble_plot
- assert bubble_plot.bubble_scale == expected_value, (
- "got %s" % bubble_plot.bubble_scale
- )
+ assert bubble_plot.bubble_scale == expected_value, "got %s" % bubble_plot.bubble_scale
@then("len(plot.categories) is {count}")
diff --git a/features/steps/presentation.py b/features/steps/presentation.py
index 2309c7462..0c1c6ba26 100644
--- a/features/steps/presentation.py
+++ b/features/steps/presentation.py
@@ -1,51 +1,50 @@
-# encoding: utf-8
-
"""Gherkin step implementations for presentation-level features."""
+from __future__ import annotations
+
+import io
import os
import zipfile
-from behave import given, when, then
+from behave import given, then, when
+from behave.runner import Context
+from helpers import saved_pptx_path, test_file, test_pptx
from pptx import Presentation
-from pptx.compat import BytesIO
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.util import Inches
-from helpers import saved_pptx_path, test_file, test_pptx
-
-
# given ===================================================
@given("a clean working directory")
-def given_clean_working_dir(context):
+def given_clean_working_dir(context: Context):
if os.path.isfile(saved_pptx_path):
os.remove(saved_pptx_path)
@given("a presentation")
-def given_a_presentation(context):
+def given_a_presentation(context: Context):
context.presentation = Presentation(test_pptx("prs-properties"))
@given("a presentation having a notes master")
-def given_a_presentation_having_a_notes_master(context):
+def given_a_presentation_having_a_notes_master(context: Context):
context.prs = Presentation(test_pptx("prs-notes"))
@given("a presentation having no notes master")
-def given_a_presentation_having_no_notes_master(context):
+def given_a_presentation_having_no_notes_master(context: Context):
context.prs = Presentation(test_pptx("prs-properties"))
@given("a presentation with external relationships")
-def given_prs_with_ext_rels(context):
+def given_prs_with_ext_rels(context: Context):
context.prs = Presentation(test_pptx("ext-rels"))
@given("an initialized pptx environment")
-def given_initialized_pptx_env(context):
+def given_initialized_pptx_env(context: Context):
pass
@@ -53,37 +52,37 @@ def given_initialized_pptx_env(context):
@when("I change the slide width and height")
-def when_change_slide_width_and_height(context):
+def when_change_slide_width_and_height(context: Context):
presentation = context.presentation
presentation.slide_width = Inches(4)
presentation.slide_height = Inches(3)
@when("I construct a Presentation instance with no path argument")
-def when_construct_default_prs(context):
+def when_construct_default_prs(context: Context):
context.prs = Presentation()
@when("I open a basic PowerPoint presentation")
-def when_open_basic_pptx(context):
+def when_open_basic_pptx(context: Context):
context.prs = Presentation(test_pptx("test"))
@when("I open a presentation extracted into a directory")
-def when_I_open_a_presentation_extracted_into_a_directory(context):
+def when_I_open_a_presentation_extracted_into_a_directory(context: Context):
context.prs = Presentation(test_file("extracted-pptx"))
@when("I open a presentation contained in a stream")
-def when_open_presentation_stream(context):
+def when_open_presentation_stream(context: Context):
with open(test_pptx("test"), "rb") as f:
- stream = BytesIO(f.read())
+ stream = io.BytesIO(f.read())
context.prs = Presentation(stream)
stream.close()
@when("I save and reload the presentation")
-def when_save_and_reload_prs(context):
+def when_save_and_reload_prs(context: Context):
if os.path.isfile(saved_pptx_path):
os.remove(saved_pptx_path)
context.prs.save(saved_pptx_path)
@@ -91,7 +90,7 @@ def when_save_and_reload_prs(context):
@when("I save that stream to a file")
-def when_save_stream_to_a_file(context):
+def when_save_stream_to_a_file(context: Context):
if os.path.isfile(saved_pptx_path):
os.remove(saved_pptx_path)
context.stream.seek(0)
@@ -100,15 +99,15 @@ def when_save_stream_to_a_file(context):
@when("I save the presentation")
-def when_save_presentation(context):
+def when_save_presentation(context: Context):
if os.path.isfile(saved_pptx_path):
os.remove(saved_pptx_path)
context.prs.save(saved_pptx_path)
@when("I save the presentation to a stream")
-def when_save_presentation_to_stream(context):
- context.stream = BytesIO()
+def when_save_presentation_to_stream(context: Context):
+ context.stream = io.BytesIO()
context.prs.save(context.stream)
@@ -116,7 +115,7 @@ def when_save_presentation_to_stream(context):
@then("I receive a presentation based on the default template")
-def then_receive_prs_based_on_def_tmpl(context):
+def then_receive_prs_based_on_def_tmpl(context: Context):
prs = context.prs
assert prs is not None
slide_masters = prs.slide_masters
@@ -128,19 +127,19 @@ def then_receive_prs_based_on_def_tmpl(context):
@then("its slide height matches its known value")
-def then_slide_height_matches_known_value(context):
+def then_slide_height_matches_known_value(context: Context):
presentation = context.presentation
assert presentation.slide_height == 6858000
@then("its slide width matches its known value")
-def then_slide_width_matches_known_value(context):
+def then_slide_width_matches_known_value(context: Context):
presentation = context.presentation
assert presentation.slide_width == 9144000
@then("I see the pptx file in the working directory")
-def then_see_pptx_file_in_working_dir(context):
+def then_see_pptx_file_in_working_dir(context: Context):
assert os.path.isfile(saved_pptx_path)
minimum = 30000
actual = os.path.getsize(saved_pptx_path)
@@ -148,7 +147,7 @@ def then_see_pptx_file_in_working_dir(context):
@then("len(notes_master.shapes) is {shape_count}")
-def then_len_notes_master_shapes_is_shape_count(context, shape_count):
+def then_len_notes_master_shapes_is_shape_count(context: Context, shape_count: str):
notes_master = context.prs.notes_master
expected = int(shape_count)
actual = len(notes_master.shapes)
@@ -156,25 +155,25 @@ def then_len_notes_master_shapes_is_shape_count(context, shape_count):
@then("prs.notes_master is a NotesMaster object")
-def then_prs_notes_master_is_a_NotesMaster_object(context):
+def then_prs_notes_master_is_a_NotesMaster_object(context: Context):
prs = context.prs
assert type(prs.notes_master).__name__ == "NotesMaster"
@then("prs.slides is a Slides object")
-def then_prs_slides_is_a_Slides_object(context):
+def then_prs_slides_is_a_Slides_object(context: Context):
prs = context.presentation
assert type(prs.slides).__name__ == "Slides"
@then("prs.slide_masters is a SlideMasters object")
-def then_prs_slide_masters_is_a_SlideMasters_object(context):
+def then_prs_slide_masters_is_a_SlideMasters_object(context: Context):
prs = context.presentation
assert type(prs.slide_masters).__name__ == "SlideMasters"
@then("the external relationships are still there")
-def then_ext_rels_are_preserved(context):
+def then_ext_rels_are_preserved(context: Context):
prs = context.prs
sld = prs.slides[0]
rel = sld.part._rels["rId2"]
@@ -184,19 +183,19 @@ def then_ext_rels_are_preserved(context):
@then("the package has the expected number of .rels parts")
-def then_the_package_has_the_expected_number_of_rels_parts(context):
+def then_the_package_has_the_expected_number_of_rels_parts(context: Context):
with zipfile.ZipFile(saved_pptx_path, "r") as z:
member_count = len(z.namelist())
assert member_count == 18, "expected 18, got %d" % member_count
@then("the slide height matches the new value")
-def then_slide_height_matches_new_value(context):
+def then_slide_height_matches_new_value(context: Context):
presentation = context.presentation
assert presentation.slide_height == Inches(3)
@then("the slide width matches the new value")
-def then_slide_width_matches_new_value(context):
+def then_slide_width_matches_new_value(context: Context):
presentation = context.presentation
assert presentation.slide_width == Inches(4)
diff --git a/features/steps/series.py b/features/steps/series.py
index 3b900e746..35965fe94 100644
--- a/features/steps/series.py
+++ b/features/steps/series.py
@@ -1,21 +1,17 @@
-# encoding: utf-8
-
"""Gherkin step implementations for chart plot features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from ast import literal_eval
from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.enum.chart import XL_MARKER_STYLE
from pptx.enum.dml import MSO_FILL_TYPE, MSO_THEME_COLOR
-from helpers import test_pptx
-
-
# given ===================================================
diff --git a/features/steps/shape.py b/features/steps/shape.py
index c5154a45b..b10ad0659 100644
--- a/features/steps/shape.py
+++ b/features/steps/shape.py
@@ -1,21 +1,17 @@
-# encoding: utf-8
-
"""Gherkin step implementations for shape-related features."""
-from __future__ import unicode_literals
+from __future__ import annotations
import hashlib
-from behave import given, when, then
+from behave import given, then, when
+from helpers import cls_qname, test_file, test_pptx
from pptx import Presentation
-from pptx.enum.shapes import MSO_SHAPE, MSO_SHAPE_TYPE, PP_MEDIA_TYPE
from pptx.action import ActionSetting
+from pptx.enum.shapes import MSO_SHAPE, MSO_SHAPE_TYPE, PP_MEDIA_TYPE
from pptx.util import Emu
-from helpers import cls_qname, test_file, test_pptx
-
-
# given ===================================================
@@ -222,9 +218,7 @@ def given_a_shape_of_known_position_and_size(context):
@when("I add a {cx} x {cy} shape at ({x}, {y})")
def when_I_add_a_cx_cy_shape_at_x_y(context, cx, cy, x, y):
- context.shape.shapes.add_shape(
- MSO_SHAPE.ROUNDED_RECTANGLE, int(x), int(y), int(cx), int(cy)
- )
+ context.shape.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, int(x), int(y), int(cx), int(cy))
@when("I assign 0.15 to shape.adjustments[0]")
@@ -272,9 +266,7 @@ def when_I_assign_value_to_connector_end_y(context, value):
@when("I assign {value} to picture.crop_{side}")
def when_I_assign_value_to_picture_crop_side(context, value, side):
- new_value = (
- None if value == "None" else float(value) if "." in value else int(value)
- )
+ new_value = None if value == "None" else float(value) if "." in value else int(value)
setattr(context.picture, "crop_%s" % side, new_value)
@@ -336,9 +328,7 @@ def then_accessing_shape_click_action_raises_TypeError(context):
except TypeError:
return
except Exception as e:
- raise AssertionError(
- "Accessing GroupShape.click_action raised %s" % type(e).__name__
- )
+ raise AssertionError("Accessing GroupShape.click_action raised %s" % type(e).__name__)
raise AssertionError("Accessing GroupShape.click_action did not raise")
@@ -658,9 +648,7 @@ def then_shape_shape_id_equals(context, value_str):
def then_shape_shape_type_is_MSO_SHAPE_TYPE_member(context, member_name):
expected_shape_type = getattr(MSO_SHAPE_TYPE, member_name)
actual_shape_type = context.shape.shape_type
- assert actual_shape_type == expected_shape_type, (
- "shape.shape_type == %s" % actual_shape_type
- )
+ assert actual_shape_type == expected_shape_type, "shape.shape_type == %s" % actual_shape_type
@then("shape.text == {value}")
diff --git a/features/steps/shapes.py b/features/steps/shapes.py
index 53a081ce6..57d5f2bb0 100644
--- a/features/steps/shapes.py
+++ b/features/steps/shapes.py
@@ -1,10 +1,11 @@
-# encoding: utf-8
-
"""Gherkin step implementations for shape collections."""
+from __future__ import annotations
+
import io
from behave import given, then, when
+from helpers import saved_pptx_path, test_file, test_image, test_pptx
from pptx import Presentation
from pptx.chart.data import CategoryChartData
@@ -13,9 +14,6 @@
from pptx.shapes.base import BaseShape
from pptx.util import Emu, Inches
-from helpers import saved_pptx_path, test_file, test_image, test_pptx
-
-
# given ===================================================
@@ -174,9 +172,7 @@ def when_I_assign_shapes_add_ole_object_to_shape(context):
@when("I assign shapes.add_picture() to shape")
def when_I_assign_shapes_add_picture_to_shape(context):
- context.shape = context.shapes.add_picture(
- test_image("sonic.gif"), Inches(1), Inches(2)
- )
+ context.shape = context.shapes.add_picture(test_image("sonic.gif"), Inches(1), Inches(2))
@when("I assign shapes.add_shape() to shape")
@@ -188,9 +184,7 @@ def when_I_assign_shapes_add_shape_to_shape(context):
@when("I assign shapes.add_textbox() to shape")
def when_I_assign_shapes_add_textbox_to_shape(context):
- context.shape = context.shapes.add_textbox(
- Inches(1), Inches(2), Inches(3), Inches(0.5)
- )
+ context.shape = context.shapes.add_textbox(Inches(1), Inches(2), Inches(3), Inches(0.5))
@when("I assign shapes.build_freeform() to builder")
@@ -229,9 +223,7 @@ def when_I_assign_True_to_shapes_turbo_add_enabled(context):
@when("I call shapes.add_chart({type_}, chart_data)")
def when_I_call_shapes_add_chart(context, type_):
chart_type = getattr(XL_CHART_TYPE, type_)
- context.chart = context.shapes.add_chart(
- chart_type, 0, 0, 0, 0, context.chart_data
- ).chart
+ context.chart = context.shapes.add_chart(chart_type, 0, 0, 0, 0, context.chart_data).chart
@when("I call shapes.add_connector(MSO_CONNECTOR.STRAIGHT, 1, 2, 3, 4)")
@@ -252,9 +244,7 @@ def when_I_call_shapes_add_movie(context):
@then("iterating shapes produces {count} objects of type {class_name}")
-def then_iterating_shapes_produces_count_objects_of_type_class_name(
- context, count, class_name
-):
+def then_iterating_shapes_produces_count_objects_of_type_class_name(context, count, class_name):
shapes = context.shapes
expected_count, expected_class_name = int(count), class_name
idx = -1
@@ -268,17 +258,13 @@ def then_iterating_shapes_produces_count_objects_of_type_class_name(
@then("iterating shapes produces {count} objects that subclass BaseShape")
-def then_iterating_shapes_produces_count_objects_that_subclass_BaseShape(
- context, count
-):
+def then_iterating_shapes_produces_count_objects_that_subclass_BaseShape(context, count):
shapes = context.shapes
expected_count = int(count)
idx = -1
for idx, shape in enumerate(shapes):
class_name = shape.__class__.__name__
- assert isinstance(shape, BaseShape), (
- "%s does not subclass BaseShape" % class_name
- )
+ assert isinstance(shape, BaseShape), "%s does not subclass BaseShape" % class_name
actual_count = idx + 1
assert actual_count == expected_count, "got %d items" % actual_count
@@ -294,9 +280,7 @@ def then_len_shapes_eq_value(context, value):
def then_shape_is_a_type_object(context, clsname):
actual_class_name = context.shape.__class__.__name__
expected_class_name = clsname
- assert actual_class_name == expected_class_name, (
- "shape is a %s object" % actual_class_name
- )
+ assert actual_class_name == expected_class_name, "shape is a %s object" % actual_class_name
@then("shapes[-1] == shape")
diff --git a/features/steps/slide.py b/features/steps/slide.py
index 3d13c7d64..a7527f36d 100644
--- a/features/steps/slide.py
+++ b/features/steps/slide.py
@@ -1,15 +1,11 @@
-# encoding: utf-8
-
"""Gherkin step implementations for slide-related features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then
-
-from pptx import Presentation
-
from helpers import test_pptx
+from pptx import Presentation
# given ===================================================
@@ -144,9 +140,7 @@ def then_slide_background_is_a_Background_object(context):
def then_slide_follow_master_background_is_value(context, value):
expected_value = {"True": True, "False": False}[value]
actual_value = context.slide.follow_master_background
- assert actual_value is expected_value, (
- "slide.follow_master_background is %s" % actual_value
- )
+ assert actual_value is expected_value, "slide.follow_master_background is %s" % actual_value
@then("slide.has_notes_slide is {value}")
@@ -173,18 +167,14 @@ def then_slide_notes_slide_is_a_NotesSlide_object(context):
def then_slide_placeholders_is_a_clsname_object(context, clsname):
actual_clsname = context.slide.placeholders.__class__.__name__
expected_clsname = clsname
- assert actual_clsname == expected_clsname, (
- "slide.placeholders is a %s object" % actual_clsname
- )
+ assert actual_clsname == expected_clsname, "slide.placeholders is a %s object" % actual_clsname
@then("slide.shapes is a {clsname} object")
def then_slide_shapes_is_a_clsname_object(context, clsname):
actual_clsname = context.slide.shapes.__class__.__name__
expected_clsname = clsname
- assert actual_clsname == expected_clsname, (
- "slide.shapes is a %s object" % actual_clsname
- )
+ assert actual_clsname == expected_clsname, "slide.shapes is a %s object" % actual_clsname
@then("slide.slide_id is 256")
diff --git a/features/steps/slides.py b/features/steps/slides.py
index 16283057c..42ef66885 100644
--- a/features/steps/slides.py
+++ b/features/steps/slides.py
@@ -1,15 +1,11 @@
-# encoding: utf-8
-
"""Gherkin step implementations for slide collection-related features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
-
-from behave import given, when, then
-
-from pptx import Presentation
+from __future__ import annotations
+from behave import given, then, when
from helpers import test_pptx
+from pptx import Presentation
# given ===================================================
diff --git a/features/steps/table.py b/features/steps/table.py
index 3aec6671e..8cbf43afd 100644
--- a/features/steps/table.py
+++ b/features/steps/table.py
@@ -1,18 +1,14 @@
-# encoding: utf-8
-
"""Gherkin step implementations for table-related features"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
-from behave import given, when, then
+from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
-from pptx.enum.text import MSO_ANCHOR # noqa
+from pptx.enum.text import MSO_ANCHOR # noqa # pyright: ignore[reportUnusedImport]
from pptx.util import Inches
-from helpers import test_pptx
-
-
# given ===================================================
@@ -57,9 +53,7 @@ def given_a_Cell_object_with_known_margins_as_cell(context):
@given("a _Cell object with {setting} vertical alignment as cell")
def given_a_Cell_object_with_setting_vertical_alignment(context, setting):
- cell_coordinates = {"inherited": (0, 1), "middle": (0, 2), "bottom": (0, 3)}[
- setting
- ]
+ cell_coordinates = {"inherited": (0, 1), "middle": (0, 2), "bottom": (0, 3)}[setting]
prs = Presentation(test_pptx("tbl-cell"))
context.cell = prs.slides[0].shapes[0].table.cell(*cell_coordinates)
diff --git a/features/steps/text.py b/features/steps/text.py
index 78c515191..5c3692b5d 100644
--- a/features/steps/text.py
+++ b/features/steps/text.py
@@ -1,18 +1,14 @@
-# encoding: utf-8
-
"""Gherkin step implementations for text-related features."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
-from behave import given, when, then
+from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
from pptx.enum.text import PP_ALIGN
from pptx.util import Emu
-from helpers import test_pptx
-
-
# given ===================================================
@@ -38,9 +34,7 @@ def given_a_paragraph_having_line_spacing_of_setting(context, setting):
@given("a paragraph having space {before_after} of {setting}")
-def given_a_paragraph_having_space_before_after_of_setting(
- context, before_after, setting
-):
+def given_a_paragraph_having_space_before_after_of_setting(context, before_after, setting):
slide_idx = {"before": 0, "after": 1}[before_after]
paragraph_idx = {"no explicit setting": 0, "6 pt": 1}[setting]
prs = Presentation(test_pptx("txt-paragraph-spacing"))
@@ -126,9 +120,7 @@ def when_I_assign_value_to_paragraph_line_spacing(context, value_str):
@when("I assign {value_str} to paragraph.space_{before_after}")
-def when_I_assign_value_to_paragraph_space_before_after(
- context, value_str, before_after
-):
+def when_I_assign_value_to_paragraph_space_before_after(context, value_str, before_after):
value = {"76200": 76200, "38100": 38100, "None": None}[value_str]
attr_name = {"before": "space_before", "after": "space_after"}[before_after]
paragraph = context.paragraph
diff --git a/features/steps/text_frame.py b/features/steps/text_frame.py
index 48401620a..49fd44092 100644
--- a/features/steps/text_frame.py
+++ b/features/steps/text_frame.py
@@ -1,26 +1,20 @@
-# encoding: utf-8
-
"""Step implementations for text frame-related features"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from behave import given, then, when
+from helpers import test_pptx
from pptx import Presentation
from pptx.enum.text import MSO_AUTO_SIZE
from pptx.util import Inches, Pt
-from helpers import test_pptx
-
-
# given ===================================================
@given("a TextFrame object as text_frame")
def given_a_text_frame(context):
- context.text_frame = (
- Presentation(test_pptx("txt-text")).slides[0].shapes[0].text_frame
- )
+ context.text_frame = Presentation(test_pptx("txt-text")).slides[0].shapes[0].text_frame
@given("a TextFrame object containing {value} as text_frame")
diff --git a/src/pptx/__init__.py b/src/pptx/__init__.py
index 7952f87bd..0c951298c 100644
--- a/src/pptx/__init__.py
+++ b/src/pptx/__init__.py
@@ -1,26 +1,20 @@
-# encoding: utf-8
-
"""Initialization module for python-pptx package."""
-__version__ = "0.6.23"
-
+from __future__ import annotations
-import pptx.exc as exceptions
import sys
+from typing import TYPE_CHECKING
-sys.modules["pptx.exceptions"] = exceptions
-del sys
-
-from pptx.api import Presentation # noqa
-
-from pptx.opc.constants import CONTENT_TYPE as CT # noqa: E402
-from pptx.opc.package import PartFactory # noqa: E402
-from pptx.parts.chart import ChartPart # noqa: E402
-from pptx.parts.coreprops import CorePropertiesPart # noqa: E402
-from pptx.parts.image import ImagePart # noqa: E402
-from pptx.parts.media import MediaPart # noqa: E402
-from pptx.parts.presentation import PresentationPart # noqa: E402
-from pptx.parts.slide import ( # noqa: E402
+import pptx.exc as exceptions
+from pptx.api import Presentation
+from pptx.opc.constants import CONTENT_TYPE as CT
+from pptx.opc.package import PartFactory
+from pptx.parts.chart import ChartPart
+from pptx.parts.coreprops import CorePropertiesPart
+from pptx.parts.image import ImagePart
+from pptx.parts.media import MediaPart
+from pptx.parts.presentation import PresentationPart
+from pptx.parts.slide import (
NotesMasterPart,
NotesSlidePart,
SlideLayoutPart,
@@ -28,7 +22,17 @@
SlidePart,
)
-content_type_to_part_class_map = {
+if TYPE_CHECKING:
+ from pptx.opc.package import Part
+
+__version__ = "0.6.23"
+
+sys.modules["pptx.exceptions"] = exceptions
+del sys
+
+__all__ = ["Presentation"]
+
+content_type_to_part_class_map: dict[str, type[Part]] = {
CT.PML_PRESENTATION_MAIN: PresentationPart,
CT.PML_PRES_MACRO_MAIN: PresentationPart,
CT.PML_TEMPLATE_MAIN: PresentationPart,
diff --git a/src/pptx/action.py b/src/pptx/action.py
index cc55e52e1..83c6ebf19 100644
--- a/src/pptx/action.py
+++ b/src/pptx/action.py
@@ -1,19 +1,35 @@
-# encoding: utf-8
-
"""Objects related to mouse click and hover actions on a shape or text."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, cast
+
from pptx.enum.action import PP_ACTION
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.shapes import Subshape
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.oxml.action import CT_Hyperlink
+ from pptx.oxml.shapes.shared import CT_NonVisualDrawingProps
+ from pptx.oxml.text import CT_TextCharacterProperties
+ from pptx.parts.slide import SlidePart
+ from pptx.shapes.base import BaseShape
+ from pptx.slide import Slide, Slides
+
class ActionSetting(Subshape):
"""Properties specifying how a shape or run reacts to mouse actions."""
- # --- The Subshape superclass provides access to the Slide Part, which is needed
- # --- to access relationships.
- def __init__(self, xPr, parent, hover=False):
+ # -- The Subshape base class provides access to the Slide Part, which is needed to access
+ # -- relationships, which is where hyperlinks live.
+
+ def __init__(
+ self,
+ xPr: CT_NonVisualDrawingProps | CT_TextCharacterProperties,
+ parent: BaseShape,
+ hover: bool = False,
+ ):
super(ActionSetting, self).__init__(parent)
# xPr is either a cNvPr or rPr element
self._element = xPr
@@ -61,7 +77,7 @@ def action(self):
}.get(action_verb, PP_ACTION.NONE)
@lazyproperty
- def hyperlink(self):
+ def hyperlink(self) -> Hyperlink:
"""
A |Hyperlink| object representing the hyperlink action defined on
this click or hover mouse event. A |Hyperlink| object is always
@@ -70,7 +86,7 @@ def hyperlink(self):
return Hyperlink(self._element, self._parent, self._hover)
@property
- def target_slide(self):
+ def target_slide(self) -> Slide | None:
"""
A reference to the slide in this presentation that is the target of
the slide jump action in this shape. Slide jump actions include
@@ -116,11 +132,13 @@ def target_slide(self):
raise ValueError("no previous slide")
return self._slides[prev_slide_idx]
elif self.action == PP_ACTION.NAMED_SLIDE:
+ assert self._hlink is not None
rId = self._hlink.rId
- return self.part.related_part(rId).slide
+ slide_part = cast("SlidePart", self.part.related_part(rId))
+ return slide_part.slide
@target_slide.setter
- def target_slide(self, slide):
+ def target_slide(self, slide: Slide | None):
self._clear_click_action()
if slide is None:
return
@@ -139,12 +157,13 @@ def _clear_click_action(self):
self._element.remove(hlink)
@property
- def _hlink(self):
+ def _hlink(self) -> CT_Hyperlink | None:
"""
- Reference to the `a:hlinkClick` or `h:hlinkHover` element for this
+ Reference to the `a:hlinkClick` or `a:hlinkHover` element for this
click action. Returns |None| if the element is not present.
"""
if self._hover:
+ assert isinstance(self._element, CT_NonVisualDrawingProps)
return self._element.hlinkHover
return self._element.hlinkClick
@@ -164,7 +183,7 @@ def _slide_index(self):
return self._slides.index(self._slide)
@lazyproperty
- def _slides(self):
+ def _slides(self) -> Slides:
"""
Reference to the slide collection for this presentation.
"""
@@ -172,11 +191,14 @@ def _slides(self):
class Hyperlink(Subshape):
- """
- Represents a hyperlink action on a shape or text run.
- """
-
- def __init__(self, xPr, parent, hover=False):
+ """Represents a hyperlink action on a shape or text run."""
+
+ def __init__(
+ self,
+ xPr: CT_NonVisualDrawingProps | CT_TextCharacterProperties,
+ parent: BaseShape,
+ hover: bool = False,
+ ):
super(Hyperlink, self).__init__(parent)
# xPr is either a cNvPr or rPr element
self._element = xPr
@@ -184,14 +206,13 @@ def __init__(self, xPr, parent, hover=False):
self._hover = hover
@property
- def address(self):
- """
- Read/write. The URL of the hyperlink. URL can be on http, https,
- mailto, or file scheme; others may work. Returns |None| if no
- hyperlink is defined, including when another action such as
- `RUN_MACRO` is defined on the object. Assigning |None| removes any
- action defined on the object, whether it is a hyperlink action or
- not.
+ def address(self) -> str | None:
+ """Read/write. The URL of the hyperlink.
+
+ URL can be on http, https, mailto, or file scheme; others may work. Returns |None| if no
+ hyperlink is defined, including when another action such as `RUN_MACRO` is defined on the
+ object. Assigning |None| removes any action defined on the object, whether it is a hyperlink
+ action or not.
"""
hlink = self._hlink
@@ -207,7 +228,7 @@ def address(self):
return self.part.target_ref(rId)
@address.setter
- def address(self, url):
+ def address(self, url: str | None):
# implements all three of add, change, and remove hyperlink
self._remove_hlink()
@@ -216,30 +237,29 @@ def address(self, url):
hlink = self._get_or_add_hlink()
hlink.rId = rId
- def _get_or_add_hlink(self):
- """
- Get the `a:hlinkClick` or `a:hlinkHover` element for the Hyperlink
- object, depending on the value of `self._hover`. Create one if not
- present.
+ def _get_or_add_hlink(self) -> CT_Hyperlink:
+ """Get the `a:hlinkClick` or `a:hlinkHover` element for the Hyperlink object.
+
+ The actual element depends on the value of `self._hover`. Create the element if not present.
"""
if self._hover:
- return self._element.get_or_add_hlinkHover()
+ return cast("CT_NonVisualDrawingProps", self._element).get_or_add_hlinkHover()
return self._element.get_or_add_hlinkClick()
@property
- def _hlink(self):
- """
- Reference to the `a:hlinkClick` or `h:hlinkHover` element for this
- click action. Returns |None| if the element is not present.
+ def _hlink(self) -> CT_Hyperlink | None:
+ """Reference to the `a:hlinkClick` or `h:hlinkHover` element for this click action.
+
+ Returns |None| if the element is not present.
"""
if self._hover:
- return self._element.hlinkHover
+ return cast("CT_NonVisualDrawingProps", self._element).hlinkHover
return self._element.hlinkClick
def _remove_hlink(self):
- """
- Remove the a:hlinkClick or a:hlinkHover element, including dropping
- any relationship it might have.
+ """Remove the a:hlinkClick or a:hlinkHover element.
+
+ Also drops any relationship it might have.
"""
hlink = self._hlink
if hlink is None:
diff --git a/src/pptx/api.py b/src/pptx/api.py
index 7670c886f..892f425ab 100644
--- a/src/pptx/api.py
+++ b/src/pptx/api.py
@@ -1,21 +1,24 @@
-# encoding: utf-8
+"""Directly exposed API classes, Presentation for now.
-"""
-Directly exposed API classes, Presentation for now. Provides some syntactic
-sugar for interacting with the pptx.presentation.Package graph and also
-provides some insulation so not so many classes in the other modules need to
-be named as internal (leading underscore).
+Provides some syntactic sugar for interacting with the pptx.presentation.Package graph and also
+provides some insulation so not so many classes in the other modules need to be named as internal
+(leading underscore).
"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import os
+from typing import IO, TYPE_CHECKING
+
+from pptx.opc.constants import CONTENT_TYPE as CT
+from pptx.package import Package
-from .opc.constants import CONTENT_TYPE as CT
-from .package import Package
+if TYPE_CHECKING:
+ from pptx import presentation
+ from pptx.parts.presentation import PresentationPart
-def Presentation(pptx=None):
+def Presentation(pptx: str | IO[bytes] | None = None) -> presentation.Presentation:
"""
Return a |Presentation| object loaded from *pptx*, where *pptx* can be
either a path to a ``.pptx`` file (a string) or a file-like object. If
@@ -34,18 +37,13 @@ def Presentation(pptx=None):
return presentation_part.presentation
-def _default_pptx_path():
- """
- Return the path to the built-in default .pptx package.
- """
+def _default_pptx_path() -> str:
+ """Return the path to the built-in default .pptx package."""
_thisdir = os.path.split(__file__)[0]
return os.path.join(_thisdir, "templates", "default.pptx")
-def _is_pptx_package(prs_part):
- """
- Return |True| if *prs_part* is a valid main document part, |False|
- otherwise.
- """
+def _is_pptx_package(prs_part: PresentationPart):
+ """Return |True| if *prs_part* is a valid main document part, |False| otherwise."""
valid_content_types = (CT.PML_PRESENTATION_MAIN, CT.PML_PRES_MACRO_MAIN)
return prs_part.content_type in valid_content_types
diff --git a/src/pptx/chart/axis.py b/src/pptx/chart/axis.py
index 66f325185..a9b877039 100644
--- a/src/pptx/chart/axis.py
+++ b/src/pptx/chart/axis.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Axis-related chart objects."""
+from __future__ import annotations
+
from pptx.dml.chtfmt import ChartFormat
from pptx.enum.chart import (
XL_AXIS_CROSSES,
diff --git a/src/pptx/chart/category.py b/src/pptx/chart/category.py
index 3d16e6f42..2c28aff5e 100644
--- a/src/pptx/chart/category.py
+++ b/src/pptx/chart/category.py
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
"""Category-related objects.
The |category.Categories| object is returned by ``Plot.categories`` and contains zero or
@@ -8,9 +6,9 @@
discovery of the depth of that hierarchy and providing means to navigate it.
"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
-from pptx.compat import Sequence
+from collections.abc import Sequence
class Categories(Sequence):
diff --git a/src/pptx/chart/chart.py b/src/pptx/chart/chart.py
index 14500a418..d73aa9338 100644
--- a/src/pptx/chart/chart.py
+++ b/src/pptx/chart/chart.py
@@ -1,13 +1,14 @@
-# encoding: utf-8
-
"""Chart-related objects such as Chart and ChartTitle."""
+from __future__ import annotations
+
+from collections.abc import Sequence
+
from pptx.chart.axis import CategoryAxis, DateAxis, ValueAxis
from pptx.chart.legend import Legend
from pptx.chart.plot import PlotFactory, PlotTypeInspector
from pptx.chart.series import SeriesCollection
from pptx.chart.xmlwriter import SeriesXmlRewriterFactory
-from pptx.compat import Sequence
from pptx.dml.chtfmt import ChartFormat
from pptx.shared import ElementProxy, PartElementProxy
from pptx.text.text import Font, TextFrame
@@ -88,12 +89,7 @@ def chart_type(self):
@lazyproperty
def font(self):
"""Font object controlling text format defaults for this chart."""
- defRPr = (
- self._chartSpace.get_or_add_txPr()
- .p_lst[0]
- .get_or_add_pPr()
- .get_or_add_defRPr()
- )
+ defRPr = self._chartSpace.get_or_add_txPr().p_lst[0].get_or_add_pPr().get_or_add_defRPr()
return Font(defRPr)
@property
diff --git a/src/pptx/chart/data.py b/src/pptx/chart/data.py
index 35e2e6b64..ec6a61f31 100644
--- a/src/pptx/chart/data.py
+++ b/src/pptx/chart/data.py
@@ -1,8 +1,9 @@
-# encoding: utf-8
-
"""ChartData and related objects."""
+from __future__ import annotations
+
import datetime
+from collections.abc import Sequence
from numbers import Number
from pptx.chart.xlsx import (
@@ -11,19 +12,17 @@
XyWorkbookWriter,
)
from pptx.chart.xmlwriter import ChartXmlWriter
-from pptx.compat import Sequence
from pptx.util import lazyproperty
class _BaseChartData(Sequence):
- """
- Base class providing common members for chart data objects. A chart data
- object serves as a proxy for the chart data table that will be written to
- an Excel worksheet; operating as a sequence of series as well as
- providing access to chart-level attributes. A chart data object is used
- as a parameter in :meth:`shapes.add_chart` and
- :meth:`Chart.replace_data`. The data structure varies between major chart
- categories such as category charts and XY charts.
+ """Base class providing common members for chart data objects.
+
+ A chart data object serves as a proxy for the chart data table that will be written to an
+ Excel worksheet; operating as a sequence of series as well as providing access to chart-level
+ attributes. A chart data object is used as a parameter in :meth:`shapes.add_chart` and
+ :meth:`Chart.replace_data`. The data structure varies between major chart categories such as
+ category charts and XY charts.
"""
def __init__(self, number_format="General"):
diff --git a/src/pptx/chart/datalabel.py b/src/pptx/chart/datalabel.py
index ec6f7cba5..af7cdf5c0 100644
--- a/src/pptx/chart/datalabel.py
+++ b/src/pptx/chart/datalabel.py
@@ -1,13 +1,9 @@
-# encoding: utf-8
+"""Data label-related objects."""
-"""
-Data label-related objects.
-"""
+from __future__ import annotations
-from __future__ import absolute_import, division, print_function, unicode_literals
-
-from ..text.text import Font, TextFrame
-from ..util import lazyproperty
+from pptx.text.text import Font, TextFrame
+from pptx.util import lazyproperty
class DataLabels(object):
diff --git a/src/pptx/chart/legend.py b/src/pptx/chart/legend.py
index 2926fae23..9bc64dbf8 100644
--- a/src/pptx/chart/legend.py
+++ b/src/pptx/chart/legend.py
@@ -1,14 +1,10 @@
-# encoding: utf-8
+"""Legend of a chart."""
-"""
-Legend of a chart.
-"""
+from __future__ import annotations
-from __future__ import absolute_import, print_function, unicode_literals
-
-from ..enum.chart import XL_LEGEND_POSITION
-from ..text.text import Font
-from ..util import lazyproperty
+from pptx.enum.chart import XL_LEGEND_POSITION
+from pptx.text.text import Font
+from pptx.util import lazyproperty
class Legend(object):
diff --git a/src/pptx/chart/marker.py b/src/pptx/chart/marker.py
index 22dc0ff19..cd4b7f024 100644
--- a/src/pptx/chart/marker.py
+++ b/src/pptx/chart/marker.py
@@ -1,15 +1,13 @@
-# encoding: utf-8
+"""Marker-related objects.
-"""
-Marker-related objects. Only the line-type charts Line, XY, and Radar have
-markers.
+Only the line-type charts Line, XY, and Radar have markers.
"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
-from ..dml.chtfmt import ChartFormat
-from ..shared import ElementProxy
-from ..util import lazyproperty
+from pptx.dml.chtfmt import ChartFormat
+from pptx.shared import ElementProxy
+from pptx.util import lazyproperty
class Marker(ElementProxy):
diff --git a/src/pptx/chart/plot.py b/src/pptx/chart/plot.py
index ce2d1167e..6e7235855 100644
--- a/src/pptx/chart/plot.py
+++ b/src/pptx/chart/plot.py
@@ -1,20 +1,18 @@
-# encoding: utf-8
+"""Plot-related objects.
-"""
-Plot-related objects. A plot is known as a chart group in the MS API. A chart
-can have more than one plot overlayed on each other, such as a line plot
-layered over a bar plot.
+A plot is known as a chart group in the MS API. A chart can have more than one plot overlayed on
+each other, such as a line plot layered over a bar plot.
"""
-from __future__ import absolute_import, print_function, unicode_literals
+from __future__ import annotations
-from .category import Categories
-from .datalabel import DataLabels
-from ..enum.chart import XL_CHART_TYPE as XL
-from ..oxml.ns import qn
-from ..oxml.simpletypes import ST_BarDir, ST_Grouping
-from .series import SeriesCollection
-from ..util import lazyproperty
+from pptx.chart.category import Categories
+from pptx.chart.datalabel import DataLabels
+from pptx.chart.series import SeriesCollection
+from pptx.enum.chart import XL_CHART_TYPE as XL
+from pptx.oxml.ns import qn
+from pptx.oxml.simpletypes import ST_BarDir, ST_Grouping
+from pptx.util import lazyproperty
class _BasePlot(object):
@@ -58,9 +56,7 @@ def data_labels(self):
"""
dLbls = self._element.dLbls
if dLbls is None:
- raise ValueError(
- "plot has no data labels, set has_data_labels = True first"
- )
+ raise ValueError("plot has no data labels, set has_data_labels = True first")
return DataLabels(dLbls)
@property
diff --git a/src/pptx/chart/point.py b/src/pptx/chart/point.py
index 258f6ae19..2d42436cb 100644
--- a/src/pptx/chart/point.py
+++ b/src/pptx/chart/point.py
@@ -1,12 +1,11 @@
-# encoding: utf-8
-
"""Data point-related objects."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from collections.abc import Sequence
from pptx.chart.datalabel import DataLabel
from pptx.chart.marker import Marker
-from pptx.compat import Sequence
from pptx.dml.chtfmt import ChartFormat
from pptx.util import lazyproperty
diff --git a/src/pptx/chart/series.py b/src/pptx/chart/series.py
index 4ae19fbc0..16112eabe 100644
--- a/src/pptx/chart/series.py
+++ b/src/pptx/chart/series.py
@@ -1,13 +1,12 @@
-# encoding: utf-8
-
"""Series-related objects."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from collections.abc import Sequence
from pptx.chart.datalabel import DataLabels
from pptx.chart.marker import Marker
from pptx.chart.point import BubblePoints, CategoryPoints, XyPoints
-from pptx.compat import Sequence
from pptx.dml.chtfmt import ChartFormat
from pptx.oxml.ns import qn
from pptx.util import lazyproperty
@@ -254,8 +253,6 @@ def _SeriesFactory(ser):
qn("c:scatterChart"): XySeries,
}[xChart_tag]
except KeyError:
- raise NotImplementedError(
- "series class for %s not yet implemented" % xChart_tag
- )
+ raise NotImplementedError("series class for %s not yet implemented" % xChart_tag)
return SeriesCls(ser)
diff --git a/src/pptx/chart/xlsx.py b/src/pptx/chart/xlsx.py
index 17e1e4fc8..30b212728 100644
--- a/src/pptx/chart/xlsx.py
+++ b/src/pptx/chart/xlsx.py
@@ -1,13 +1,12 @@
-# encoding: utf-8
-
"""Chart builder and related objects."""
+from __future__ import annotations
+
+import io
from contextlib import contextmanager
from xlsxwriter import Workbook
-from ..compat import BytesIO
-
class _BaseWorkbookWriter(object):
"""Base class for workbook writers, providing shared members."""
@@ -19,7 +18,7 @@ def __init__(self, chart_data):
@property
def xlsx_blob(self):
"""bytes for Excel file containing chart_data."""
- xlsx_file = BytesIO()
+ xlsx_file = io.BytesIO()
with self._open_worksheet(xlsx_file) as (workbook, worksheet):
self._populate_worksheet(workbook, worksheet)
return xlsx_file.getvalue()
@@ -29,7 +28,7 @@ def _open_worksheet(self, xlsx_file):
"""
Enable XlsxWriter Worksheet object to be opened, operated on, and
then automatically closed within a `with` statement. A filename or
- stream object (such as a ``BytesIO`` instance) is expected as
+ stream object (such as an `io.BytesIO` instance) is expected as
*xlsx_file*.
"""
workbook = Workbook(xlsx_file, {"in_memory": True})
@@ -225,13 +224,9 @@ def _populate_worksheet(self, workbook, worksheet):
table, X values in column A and Y values in column B. Place the
series label in the first (heading) cell of the column.
"""
- chart_num_format = workbook.add_format(
- {"num_format": self._chart_data.number_format}
- )
+ chart_num_format = workbook.add_format({"num_format": self._chart_data.number_format})
for series in self._chart_data:
- series_num_format = workbook.add_format(
- {"num_format": series.number_format}
- )
+ series_num_format = workbook.add_format({"num_format": series.number_format})
offset = self.series_table_row_offset(series)
# write X values
worksheet.write_column(offset + 1, 0, series.x_values, chart_num_format)
@@ -263,13 +258,9 @@ def _populate_worksheet(self, workbook, worksheet):
column C. Place the series label in the first (heading) cell of the
values column.
"""
- chart_num_format = workbook.add_format(
- {"num_format": self._chart_data.number_format}
- )
+ chart_num_format = workbook.add_format({"num_format": self._chart_data.number_format})
for series in self._chart_data:
- series_num_format = workbook.add_format(
- {"num_format": series.number_format}
- )
+ series_num_format = workbook.add_format({"num_format": series.number_format})
offset = self.series_table_row_offset(series)
# write X values
worksheet.write_column(offset + 1, 0, series.x_values, chart_num_format)
diff --git a/src/pptx/chart/xmlwriter.py b/src/pptx/chart/xmlwriter.py
index c485a4b88..703c53dd5 100644
--- a/src/pptx/chart/xmlwriter.py
+++ b/src/pptx/chart/xmlwriter.py
@@ -1,18 +1,13 @@
-# encoding: utf-8
+"""Composers for default chart XML for various chart types."""
-"""
-Composers for default chart XML for various chart types.
-"""
-
-from __future__ import absolute_import, print_function, unicode_literals
+from __future__ import annotations
from copy import deepcopy
from xml.sax.saxutils import escape
-from ..compat import to_unicode
-from ..enum.chart import XL_CHART_TYPE
-from ..oxml import parse_xml
-from ..oxml.ns import nsdecls
+from pptx.enum.chart import XL_CHART_TYPE
+from pptx.oxml import parse_xml
+from pptx.oxml.ns import nsdecls
def ChartXmlWriter(chart_type, chart_data):
@@ -54,9 +49,7 @@ def ChartXmlWriter(chart_type, chart_data):
XL_CT.XY_SCATTER_SMOOTH_NO_MARKERS: _XyChartXmlWriter,
}[chart_type]
except KeyError:
- raise NotImplementedError(
- "XML writer for chart type %s not yet implemented" % chart_type
- )
+ raise NotImplementedError("XML writer for chart type %s not yet implemented" % chart_type)
return BuilderCls(chart_type, chart_data)
@@ -136,9 +129,7 @@ def numRef_xml(self, wksht_ref, number_format, values):
"{pt_xml}"
" \n"
" \n"
- ).format(
- **{"wksht_ref": wksht_ref, "number_format": number_format, "pt_xml": pt_xml}
- )
+ ).format(**{"wksht_ref": wksht_ref, "number_format": number_format, "pt_xml": pt_xml})
def pt_xml(self, values):
"""
@@ -149,9 +140,7 @@ def pt_xml(self, values):
in the overall data point sequence of the chart and is started at
*offset*.
"""
- xml = (' \n').format(
- pt_count=len(values)
- )
+ xml = (' \n').format(pt_count=len(values))
pt_tmpl = (
' \n'
@@ -289,9 +278,7 @@ def _trim_ser_count_by(self, plotArea, count):
for ser in extra_sers:
parent = ser.getparent()
parent.remove(ser)
- extra_xCharts = [
- xChart for xChart in plotArea.iter_xCharts() if len(xChart.sers) == 0
- ]
+ extra_xCharts = [xChart for xChart in plotArea.iter_xCharts() if len(xChart.sers) == 0]
for xChart in extra_xCharts:
parent = xChart.getparent()
parent.remove(xChart)
@@ -529,9 +516,7 @@ def _barDir_xml(self):
return ' \n'
elif self._chart_type in col_types:
return ' \n'
- raise NotImplementedError(
- "no _barDir_xml() for chart type %s" % self._chart_type
- )
+ raise NotImplementedError("no _barDir_xml() for chart type %s" % self._chart_type)
@property
def _cat_ax_pos(self):
@@ -601,9 +586,7 @@ def _grouping_xml(self):
return ' \n'
elif self._chart_type in percentStacked_types:
return ' \n'
- raise NotImplementedError(
- "no _grouping_xml() for chart type %s" % self._chart_type
- )
+ raise NotImplementedError("no _grouping_xml() for chart type %s" % self._chart_type)
@property
def _overlap_xml(self):
@@ -870,9 +853,7 @@ def _grouping_xml(self):
return ' \n'
elif self._chart_type in percentStacked_types:
return ' \n'
- raise NotImplementedError(
- "no _grouping_xml() for chart type %s" % self._chart_type
- )
+ raise NotImplementedError("no _grouping_xml() for chart type %s" % self._chart_type)
@property
def _marker_xml(self):
@@ -1532,9 +1513,7 @@ def _cat_pt_xml(self):
' \n'
" {cat_label}\n"
" \n"
- ).format(
- **{"cat_idx": idx, "cat_label": escape(to_unicode(category.label))}
- )
+ ).format(**{"cat_idx": idx, "cat_label": escape(str(category.label))})
return xml
@property
@@ -1573,9 +1552,9 @@ def lvl_pt_xml(level):
xml = ""
for level in categories.levels:
- xml += (
- " \n" "{lvl_pt_xml}" " \n"
- ).format(**{"lvl_pt_xml": lvl_pt_xml(level)})
+ xml += (" \n" "{lvl_pt_xml}" " \n").format(
+ **{"lvl_pt_xml": lvl_pt_xml(level)}
+ )
return xml
@property
@@ -1793,11 +1772,7 @@ def _bubbleSize_tmpl(self):
containing the bubble size values and their spreadsheet range
reference.
"""
- return (
- " \n"
- "{numRef_xml}"
- " \n"
- )
+ return " \n" "{numRef_xml}" " \n"
class _BubbleSeriesXmlRewriter(_BaseSeriesXmlRewriter):
diff --git a/src/pptx/compat/__init__.py b/src/pptx/compat/__init__.py
deleted file mode 100644
index 198dc6a0e..000000000
--- a/src/pptx/compat/__init__.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# encoding: utf-8
-
-"""Provides Python 2/3 compatibility objects."""
-
-import sys
-
-try:
- from collections.abc import Container, Mapping, Sequence
-except ImportError:
- from collections import Container, Mapping, Sequence
-
-if sys.version_info >= (3, 0):
- from .python3 import ( # noqa
- BytesIO,
- Unicode,
- is_integer,
- is_string,
- is_unicode,
- to_unicode,
- )
-else:
- from .python2 import ( # noqa
- BytesIO,
- Unicode,
- is_integer,
- is_string,
- is_unicode,
- to_unicode,
- )
-
-__all__ = [
- "BytesIO",
- "Container",
- "Mapping",
- "Sequence",
- "Unicode",
- "is_integer",
- "is_string",
- "is_unicode",
- "to_unicode",
-]
diff --git a/src/pptx/compat/python2.py b/src/pptx/compat/python2.py
deleted file mode 100644
index 34e2535d5..000000000
--- a/src/pptx/compat/python2.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# encoding: utf-8
-
-"""Provides Python 2 compatibility objects."""
-
-from StringIO import StringIO as BytesIO # noqa
-
-
-def is_integer(obj):
- """Return True if *obj* is an integer (int, long), False otherwise."""
- return isinstance(obj, (int, long)) # noqa F821
-
-
-def is_string(obj):
- """Return True if *obj* is a string, False otherwise."""
- return isinstance(obj, basestring) # noqa F821
-
-
-def is_unicode(obj):
- """Return True if *obj* is a unicode string, False otherwise."""
- return isinstance(obj, unicode) # noqa F821
-
-
-def to_unicode(text):
- """Return *text* as a unicode string.
-
- *text* can be a 7-bit ASCII string, a UTF-8 encoded 8-bit string, or unicode. String
- values are converted to unicode assuming UTF-8 encoding. Unicode values are returned
- unchanged.
- """
- # both str and unicode inherit from basestring
- if not isinstance(text, basestring): # noqa F821
- tmpl = "expected unicode or UTF-8 (or ASCII) encoded str, got %s value %s"
- raise TypeError(tmpl % (type(text), text))
- # return unicode strings unchanged
- if isinstance(text, unicode): # noqa F821
- return text
- # otherwise assume UTF-8 encoding, which also works for ASCII
- return unicode(text, "utf-8") # noqa F821
-
-
-Unicode = unicode # noqa F821
diff --git a/src/pptx/compat/python3.py b/src/pptx/compat/python3.py
deleted file mode 100644
index 85fce2376..000000000
--- a/src/pptx/compat/python3.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# encoding: utf-8
-
-"""Provides Python 3 compatibility objects."""
-
-from io import BytesIO # noqa
-
-
-def is_integer(obj):
- """
- Return True if *obj* is an int, False otherwise.
- """
- return isinstance(obj, int)
-
-
-def is_string(obj):
- """
- Return True if *obj* is a string, False otherwise.
- """
- return isinstance(obj, str)
-
-
-def is_unicode(obj):
- """
- Return True if *obj* is a unicode string, False otherwise.
- """
- return isinstance(obj, str)
-
-
-def to_unicode(text):
- """Return *text* as a (unicode) str.
-
- *text* can be str or bytes. A bytes object is assumed to be encoded as UTF-8.
- If *text* is a str object it is returned unchanged.
- """
- if isinstance(text, str):
- return text
- try:
- return text.decode("utf-8")
- except AttributeError:
- raise TypeError("expected unicode string, got %s value %s" % (type(text), text))
-
-
-Unicode = str
diff --git a/src/pptx/dml/chtfmt.py b/src/pptx/dml/chtfmt.py
index dcecb63ab..c37e4844d 100644
--- a/src/pptx/dml/chtfmt.py
+++ b/src/pptx/dml/chtfmt.py
@@ -1,17 +1,15 @@
-# encoding: utf-8
+"""|ChartFormat| and related objects.
-"""
-|ChartFormat| and related objects. |ChartFormat| acts as proxy for the `spPr`
-element, which provides visual shape properties such as line and fill for
-chart elements.
+|ChartFormat| acts as proxy for the `spPr` element, which provides visual shape properties such as
+line and fill for chart elements.
"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
-from .fill import FillFormat
-from .line import LineFormat
-from ..shared import ElementProxy
-from ..util import lazyproperty
+from pptx.dml.fill import FillFormat
+from pptx.dml.line import LineFormat
+from pptx.shared import ElementProxy
+from pptx.util import lazyproperty
class ChartFormat(ElementProxy):
diff --git a/src/pptx/dml/color.py b/src/pptx/dml/color.py
index 71e619c9b..54155823d 100644
--- a/src/pptx/dml/color.py
+++ b/src/pptx/dml/color.py
@@ -1,13 +1,9 @@
-# encoding: utf-8
+"""DrawingML objects related to color, ColorFormat being the most prominent."""
-"""
-DrawingML objects related to color, ColorFormat being the most prominent.
-"""
+from __future__ import annotations
-from __future__ import absolute_import, print_function, unicode_literals
-
-from ..enum.dml import MSO_COLOR_TYPE, MSO_THEME_COLOR
-from ..oxml.dml.color import (
+from pptx.enum.dml import MSO_COLOR_TYPE, MSO_THEME_COLOR
+from pptx.oxml.dml.color import (
CT_HslColor,
CT_PresetColor,
CT_SchemeColor,
diff --git a/src/pptx/dml/effect.py b/src/pptx/dml/effect.py
index 65753014a..9df69ce49 100644
--- a/src/pptx/dml/effect.py
+++ b/src/pptx/dml/effect.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
-
"""Visual effects on a shape such as shadow, glow, and reflection."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
class ShadowFormat(object):
diff --git a/src/pptx/dml/fill.py b/src/pptx/dml/fill.py
index e84bea9c6..8212af9e8 100644
--- a/src/pptx/dml/fill.py
+++ b/src/pptx/dml/fill.py
@@ -1,10 +1,10 @@
-# encoding: utf-8
-
"""DrawingML objects related to fill."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from collections.abc import Sequence
+from typing import TYPE_CHECKING
-from pptx.compat import Sequence
from pptx.dml.color import ColorFormat
from pptx.enum.dml import MSO_FILL
from pptx.oxml.dml.fill import (
@@ -15,23 +15,28 @@
CT_PatternFillProperties,
CT_SolidColorFillProperties,
)
+from pptx.oxml.xmlchemy import BaseOxmlElement
from pptx.shared import ElementProxy
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.enum.dml import MSO_FILL_TYPE
+ from pptx.oxml.xmlchemy import BaseOxmlElement
+
class FillFormat(object):
- """
- Provides access to the current fill properties object and provides
- methods to change the fill type.
+ """Provides access to the current fill properties.
+
+ Also provides methods to change the fill type.
"""
- def __init__(self, eg_fill_properties_parent, fill_obj):
+ def __init__(self, eg_fill_properties_parent: BaseOxmlElement, fill_obj: _Fill):
super(FillFormat, self).__init__()
self._xPr = eg_fill_properties_parent
self._fill = fill_obj
@classmethod
- def from_fill_parent(cls, eg_fillProperties_parent):
+ def from_fill_parent(cls, eg_fillProperties_parent: BaseOxmlElement) -> FillFormat:
"""
Return a |FillFormat| instance initialized to the settings contained
in *eg_fillProperties_parent*, which must be an element having
@@ -151,11 +156,8 @@ def solid(self):
self._fill = _SolidFill(solidFill)
@property
- def type(self):
- """
- Return a value from the :ref:`MsoFillType` enumeration corresponding
- to the type of this fill.
- """
+ def type(self) -> MSO_FILL_TYPE:
+ """The type of this fill, e.g. `MSO_FILL_TYPE.SOLID`."""
return self._fill.type
@@ -194,10 +196,7 @@ def back_color(self):
@property
def fore_color(self):
"""Raise TypeError for types that do not override this property."""
- tmpl = (
- "fill type %s has no foreground color, call .solid() or .pattern"
- "ed() first"
- )
+ tmpl = "fill type %s has no foreground color, call .solid() or .pattern" "ed() first"
raise TypeError(tmpl % self.__class__.__name__)
@property
@@ -207,9 +206,10 @@ def pattern(self):
raise TypeError(tmpl % self.__class__.__name__)
@property
- def type(self): # pragma: no cover
- tmpl = ".type property must be implemented on %s"
- raise NotImplementedError(tmpl % self.__class__.__name__)
+ def type(self) -> MSO_FILL_TYPE: # pragma: no cover
+ raise NotImplementedError(
+ f".type property must be implemented on {self.__class__.__name__}"
+ )
class _BlipFill(_Fill):
@@ -251,9 +251,7 @@ def gradient_angle(self):
# Since the UI is consistent with trigonometry conventions, we
# respect that in the API.
clockwise_angle = lin.ang
- counter_clockwise_angle = (
- 0.0 if clockwise_angle == 0.0 else (360.0 - clockwise_angle)
- )
+ counter_clockwise_angle = 0.0 if clockwise_angle == 0.0 else (360.0 - clockwise_angle)
return counter_clockwise_angle
@gradient_angle.setter
diff --git a/src/pptx/dml/line.py b/src/pptx/dml/line.py
index 698c7f633..82be47a40 100644
--- a/src/pptx/dml/line.py
+++ b/src/pptx/dml/line.py
@@ -1,12 +1,10 @@
-# encoding: utf-8
-
"""DrawingML objects related to line formatting."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
-from ..enum.dml import MSO_FILL
-from .fill import FillFormat
-from ..util import Emu, lazyproperty
+from pptx.dml.fill import FillFormat
+from pptx.enum.dml import MSO_FILL
+from pptx.util import Emu, lazyproperty
class LineFormat(object):
diff --git a/src/pptx/exc.py b/src/pptx/exc.py
index 8641fe44f..0a1e03b81 100644
--- a/src/pptx/exc.py
+++ b/src/pptx/exc.py
@@ -1,11 +1,10 @@
-# encoding: utf-8
-
-"""
-Exceptions used with python-pptx.
+"""Exceptions used with python-pptx.
The base exception class is PythonPptxError.
"""
+from __future__ import annotations
+
class PythonPptxError(Exception):
"""Generic error class."""
diff --git a/src/pptx/media.py b/src/pptx/media.py
index c5adf24ea..7aaf47ca1 100644
--- a/src/pptx/media.py
+++ b/src/pptx/media.py
@@ -1,40 +1,38 @@
-# encoding: utf-8
-
"""Objects related to images, audio, and video."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import base64
import hashlib
import os
+from typing import IO
-from .compat import is_string
-from .opc.constants import CONTENT_TYPE as CT
-from .util import lazyproperty
+from pptx.opc.constants import CONTENT_TYPE as CT
+from pptx.util import lazyproperty
class Video(object):
"""Immutable value object representing a video such as MP4."""
- def __init__(self, blob, mime_type, filename):
+ def __init__(self, blob: bytes, mime_type: str | None, filename: str | None):
super(Video, self).__init__()
self._blob = blob
self._mime_type = mime_type
self._filename = filename
@classmethod
- def from_blob(cls, blob, mime_type, filename=None):
+ def from_blob(cls, blob: bytes, mime_type: str | None, filename: str | None = None):
"""Return a new |Video| object loaded from image binary in *blob*."""
return cls(blob, mime_type, filename)
@classmethod
- def from_path_or_file_like(cls, movie_file, mime_type):
+ def from_path_or_file_like(cls, movie_file: str | IO[bytes], mime_type: str | None) -> Video:
"""Return a new |Video| object containing video in *movie_file*.
*movie_file* can be either a path (string) or a file-like
(e.g. StringIO) object.
"""
- if is_string(movie_file):
+ if isinstance(movie_file, str):
# treat movie_file as a path
with open(movie_file, "rb") as f:
blob = f.read()
@@ -79,7 +77,7 @@ def ext(self):
}.get(self._mime_type, "vid")
@property
- def filename(self):
+ def filename(self) -> str:
"""Return a filename.ext string appropriate to this video.
The base filename from the original path is used if this image was
diff --git a/src/pptx/opc/constants.py b/src/pptx/opc/constants.py
index 9eef0ee23..e1b08a93a 100644
--- a/src/pptx/opc/constants.py
+++ b/src/pptx/opc/constants.py
@@ -1,34 +1,24 @@
-# encoding: utf-8
-
"""Constant values related to the Open Packaging Convention.
In particular, this includes content (MIME) types and relationship types.
"""
+from __future__ import annotations
+
-class CONTENT_TYPE(object):
+class CONTENT_TYPE:
"""Content type URIs (like MIME-types) that specify a part's format."""
ASF = "video/x-ms-asf"
AVI = "video/avi"
BMP = "image/bmp"
DML_CHART = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
- DML_CHARTSHAPES = (
- "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"
- )
- DML_DIAGRAM_COLORS = (
- "application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml"
- )
- DML_DIAGRAM_DATA = (
- "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"
- )
+ DML_CHARTSHAPES = "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"
+ DML_DIAGRAM_COLORS = "application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml"
+ DML_DIAGRAM_DATA = "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"
DML_DIAGRAM_DRAWING = "application/vnd.ms-office.drawingml.diagramDrawing+xml"
- DML_DIAGRAM_LAYOUT = (
- "application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml"
- )
- DML_DIAGRAM_STYLE = (
- "application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml"
- )
+ DML_DIAGRAM_LAYOUT = "application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml"
+ DML_DIAGRAM_STYLE = "application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml"
GIF = "image/gif"
INK = "application/inkml+xml"
JPEG = "image/jpeg"
@@ -40,9 +30,7 @@ class CONTENT_TYPE(object):
OFC_CHART_COLORS = "application/vnd.ms-office.chartcolorstyle+xml"
OFC_CHART_EX = "application/vnd.ms-office.chartex+xml"
OFC_CHART_STYLE = "application/vnd.ms-office.chartstyle+xml"
- OFC_CUSTOM_PROPERTIES = (
- "application/vnd.openxmlformats-officedocument.custom-properties+xml"
- )
+ OFC_CUSTOM_PROPERTIES = "application/vnd.openxmlformats-officedocument.custom-properties+xml"
OFC_CUSTOM_XML_PROPERTIES = (
"application/vnd.openxmlformats-officedocument.customXmlProperties+xml"
)
@@ -53,59 +41,40 @@ class CONTENT_TYPE(object):
OFC_OLE_OBJECT = "application/vnd.openxmlformats-officedocument.oleObject"
OFC_PACKAGE = "application/vnd.openxmlformats-officedocument.package"
OFC_THEME = "application/vnd.openxmlformats-officedocument.theme+xml"
- OFC_THEME_OVERRIDE = (
- "application/vnd.openxmlformats-officedocument.themeOverride+xml"
- )
+ OFC_THEME_OVERRIDE = "application/vnd.openxmlformats-officedocument.themeOverride+xml"
OFC_VML_DRAWING = "application/vnd.openxmlformats-officedocument.vmlDrawing"
OPC_CORE_PROPERTIES = "application/vnd.openxmlformats-package.core-properties+xml"
OPC_DIGITAL_SIGNATURE_CERTIFICATE = (
"application/vnd.openxmlformats-package.digital-signature-certificate"
)
- OPC_DIGITAL_SIGNATURE_ORIGIN = (
- "application/vnd.openxmlformats-package.digital-signature-origin"
- )
+ OPC_DIGITAL_SIGNATURE_ORIGIN = "application/vnd.openxmlformats-package.digital-signature-origin"
OPC_DIGITAL_SIGNATURE_XMLSIGNATURE = (
"application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"
)
OPC_RELATIONSHIPS = "application/vnd.openxmlformats-package.relationships+xml"
- PML_COMMENTS = (
- "application/vnd.openxmlformats-officedocument.presentationml.comments+xml"
- )
+ PML_COMMENTS = "application/vnd.openxmlformats-officedocument.presentationml.comments+xml"
PML_COMMENT_AUTHORS = (
- "application/vnd.openxmlformats-officedocument.presentationml.commen"
- "tAuthors+xml"
+ "application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml"
)
PML_HANDOUT_MASTER = (
- "application/vnd.openxmlformats-officedocument.presentationml.handou"
- "tMaster+xml"
+ "application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml"
)
PML_NOTES_MASTER = (
- "application/vnd.openxmlformats-officedocument.presentationml.notesM"
- "aster+xml"
- )
- PML_NOTES_SLIDE = (
- "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml"
- )
- PML_PRESENTATION = (
- "application/vnd.openxmlformats-officedocument.presentationml.presentation"
+ "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml"
)
+ PML_NOTES_SLIDE = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml"
+ PML_PRESENTATION = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
PML_PRESENTATION_MAIN = (
- "application/vnd.openxmlformats-officedocument.presentationml.presentation.ma"
- "in+xml"
- )
- PML_PRES_MACRO_MAIN = (
- "application/vnd.ms-powerpoint.presentation.macroEnabled.main+xml"
- )
- PML_PRES_PROPS = (
- "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"
)
+ PML_PRES_MACRO_MAIN = "application/vnd.ms-powerpoint.presentation.macroEnabled.main+xml"
+ PML_PRES_PROPS = "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"
PML_PRINTER_SETTINGS = (
"application/vnd.openxmlformats-officedocument.presentationml.printerSettings"
)
PML_SLIDE = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"
PML_SLIDESHOW_MAIN = (
- "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+"
- "xml"
+ "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml"
)
PML_SLIDE_LAYOUT = (
"application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"
@@ -114,146 +83,88 @@ class CONTENT_TYPE(object):
"application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"
)
PML_SLIDE_UPDATE_INFO = (
- "application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo"
- "+xml"
+ "application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml"
)
PML_TABLE_STYLES = (
"application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml"
)
PML_TAGS = "application/vnd.openxmlformats-officedocument.presentationml.tags+xml"
PML_TEMPLATE_MAIN = (
- "application/vnd.openxmlformats-officedocument.presentationml.template.main+x"
- "ml"
- )
- PML_VIEW_PROPS = (
- "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml"
+ "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml"
)
+ PML_VIEW_PROPS = "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml"
PNG = "image/png"
- SML_CALC_CHAIN = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"
- )
- SML_CHARTSHEET = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"
- )
- SML_COMMENTS = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
- )
- SML_CONNECTIONS = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml"
- )
+ SML_CALC_CHAIN = "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"
+ SML_CHARTSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"
+ SML_COMMENTS = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
+ SML_CONNECTIONS = "application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml"
SML_CUSTOM_PROPERTY = (
"application/vnd.openxmlformats-officedocument.spreadsheetml.customProperty"
)
- SML_DIALOGSHEET = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml"
- )
+ SML_DIALOGSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml"
SML_EXTERNAL_LINK = (
"application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"
)
SML_PIVOT_CACHE_DEFINITION = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefini"
- "tion+xml"
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"
)
SML_PIVOT_CACHE_RECORDS = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecord"
- "s+xml"
- )
- SML_PIVOT_TABLE = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"
)
+ SML_PIVOT_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
SML_PRINTER_SETTINGS = (
"application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings"
)
- SML_QUERY_TABLE = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml"
- )
+ SML_QUERY_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml"
SML_REVISION_HEADERS = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+"
- "xml"
- )
- SML_REVISION_LOG = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml"
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml"
)
+ SML_REVISION_LOG = "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml"
SML_SHARED_STRINGS = (
"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"
)
SML_SHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
- SML_SHEET_MAIN = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
- )
+ SML_SHEET_MAIN = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
SML_SHEET_METADATA = (
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml"
)
- SML_STYLES = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
- )
+ SML_STYLES = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
SML_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"
SML_TABLE_SINGLE_CELLS = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells"
- "+xml"
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml"
)
SML_TEMPLATE_MAIN = (
"application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"
)
- SML_USER_NAMES = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml"
- )
+ SML_USER_NAMES = "application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml"
SML_VOLATILE_DEPENDENCIES = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependen"
- "cies+xml"
- )
- SML_WORKSHEET = (
- "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml"
)
+ SML_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
SWF = "application/x-shockwave-flash"
TIFF = "image/tiff"
VIDEO = "video/unknown"
- WML_COMMENTS = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"
- )
- WML_DOCUMENT = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
- )
+ WML_COMMENTS = "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"
+ WML_DOCUMENT = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
WML_DOCUMENT_GLOSSARY = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document.glos"
- "sary+xml"
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml"
)
WML_DOCUMENT_MAIN = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main"
- "+xml"
- )
- WML_ENDNOTES = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml"
- )
- WML_FONT_TABLE = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"
- )
- WML_FOOTER = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"
- )
- WML_FOOTNOTES = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.foot"
- "notes+xml"
- )
- WML_HEADER = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"
- )
- WML_NUMBERING = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"
- )
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"
+ )
+ WML_ENDNOTES = "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml"
+ WML_FONT_TABLE = "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"
+ WML_FOOTER = "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"
+ WML_FOOTNOTES = "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"
+ WML_HEADER = "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"
+ WML_NUMBERING = "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"
WML_PRINTER_SETTINGS = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.printerSettin"
- "gs"
- )
- WML_SETTINGS = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"
- )
- WML_STYLES = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.printerSettings"
)
+ WML_SETTINGS = "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"
+ WML_STYLES = "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"
WML_WEB_SETTINGS = (
- "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+x"
- "ml"
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml"
)
WMV = "video/x-ms-wmv"
XML = "application/xml"
@@ -264,171 +175,100 @@ class CONTENT_TYPE(object):
X_WMF = "image/x-wmf"
-class NAMESPACE(object):
+class NAMESPACE:
"""Constant values for OPC XML namespaces"""
DML_WORDPROCESSING_DRAWING = (
"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
)
- OFC_RELATIONSHIPS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
- )
+ OFC_RELATIONSHIPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
OPC_RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships"
OPC_CONTENT_TYPES = "http://schemas.openxmlformats.org/package/2006/content-types"
WML_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
-class RELATIONSHIP_TARGET_MODE(object):
+class RELATIONSHIP_TARGET_MODE:
"""Open XML relationship target modes"""
EXTERNAL = "External"
INTERNAL = "Internal"
-class RELATIONSHIP_TYPE(object):
+class RELATIONSHIP_TYPE:
AUDIO = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio"
- A_F_CHUNK = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk"
- )
- CALC_CHAIN = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain"
- )
+ A_F_CHUNK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk"
+ CALC_CHAIN = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain"
CERTIFICATE = (
"http://schemas.openxmlformats.org/package/2006/relationships/digital-signatu"
"re/certificate"
)
CHART = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"
- CHARTSHEET = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet"
- )
- CHART_COLOR_STYLE = (
- "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle"
- )
+ CHARTSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet"
+ CHART_COLOR_STYLE = "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle"
CHART_USER_SHAPES = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUse"
- "rShapes"
- )
- COMMENTS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes"
)
+ COMMENTS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
COMMENT_AUTHORS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentA"
- "uthors"
- )
- CONNECTIONS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connecti"
- "ons"
- )
- CONTROL = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/control"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentAuthors"
)
+ CONNECTIONS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections"
+ CONTROL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/control"
CORE_PROPERTIES = (
- "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-p"
- "roperties"
+ "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
)
CUSTOM_PROPERTIES = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-p"
- "roperties"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"
)
CUSTOM_PROPERTY = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
- "/customProperty"
- )
- CUSTOM_XML = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customProperty"
)
+ CUSTOM_XML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml"
CUSTOM_XML_PROPS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXm"
- "lProps"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps"
)
DIAGRAM_COLORS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramC"
- "olors"
- )
- DIAGRAM_DATA = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramD"
- "ata"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramColors"
)
+ DIAGRAM_DATA = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramData"
DIAGRAM_LAYOUT = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramL"
- "ayout"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramLayout"
)
DIAGRAM_QUICK_STYLE = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramQ"
- "uickStyle"
- )
- DIALOGSHEET = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsh"
- "eet"
- )
- DRAWING = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
- )
- ENDNOTES = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramQuickStyle"
)
+ DIALOGSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet"
+ DRAWING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
+ ENDNOTES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes"
EXTENDED_PROPERTIES = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended"
- "-properties"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"
)
EXTERNAL_LINK = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/external"
- "Link"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink"
)
FONT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font"
- FONT_TABLE = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable"
- )
- FOOTER = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer"
- )
- FOOTNOTES = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes"
- )
+ FONT_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable"
+ FOOTER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer"
+ FOOTNOTES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes"
GLOSSARY_DOCUMENT = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/glossary"
- "Document"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/glossaryDocument"
)
HANDOUT_MASTER = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/handoutM"
- "aster"
- )
- HEADER = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header"
- )
- HYPERLINK = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlin"
- "k"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/handoutMaster"
)
+ HEADER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header"
+ HYPERLINK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
MEDIA = "http://schemas.microsoft.com/office/2007/relationships/media"
- NOTES_MASTER = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMas"
- "ter"
- )
- NOTES_SLIDE = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSli"
- "de"
- )
- NUMBERING = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numberin"
- "g"
- )
+ NOTES_MASTER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster"
+ NOTES_SLIDE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide"
+ NUMBERING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering"
OFFICE_DOCUMENT = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDo"
- "cument"
- )
- OLE_OBJECT = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObjec"
- "t"
- )
- ORIGIN = (
- "http://schemas.openxmlformats.org/package/2006/relationships/digital-signatu"
- "re/origin"
- )
- PACKAGE = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
)
+ OLE_OBJECT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject"
+ ORIGIN = "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin"
+ PACKAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package"
PIVOT_CACHE_DEFINITION = (
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCac"
"heDefinition"
@@ -437,105 +277,55 @@ class RELATIONSHIP_TYPE(object):
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/spreadsh"
"eetml/pivotCacheRecords"
)
- PIVOT_TABLE = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTab"
- "le"
- )
- PRES_PROPS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProp"
- "s"
- )
+ PIVOT_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
+ PRES_PROPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps"
PRINTER_SETTINGS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerS"
- "ettings"
- )
- QUERY_TABLE = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/queryTab"
- "le"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings"
)
+ QUERY_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/queryTable"
REVISION_HEADERS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/revision"
- "Headers"
- )
- REVISION_LOG = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/revision"
- "Log"
- )
- SETTINGS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/revisionHeaders"
)
+ REVISION_LOG = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/revisionLog"
+ SETTINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings"
SHARED_STRINGS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedSt"
- "rings"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
)
SHEET_METADATA = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMet"
- "adata"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata"
)
SIGNATURE = (
"http://schemas.openxmlformats.org/package/2006/relationships/digital-signatu"
"re/signature"
)
SLIDE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
- SLIDE_LAYOUT = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLay"
- "out"
- )
- SLIDE_MASTER = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMas"
- "ter"
- )
+ SLIDE_LAYOUT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"
+ SLIDE_MASTER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"
SLIDE_UPDATE_INFO = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideUpd"
- "ateInfo"
- )
- STYLES = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideUpdateInfo"
)
+ STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"
TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
TABLE_SINGLE_CELLS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableSin"
- "gleCells"
- )
- TABLE_STYLES = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableSty"
- "les"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableSingleCells"
)
+ TABLE_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles"
TAGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tags"
THEME = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"
THEME_OVERRIDE = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOve"
- "rride"
- )
- THUMBNAIL = (
- "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbn"
- "ail"
- )
- USERNAMES = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/username"
- "s"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride"
)
+ THUMBNAIL = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail"
+ USERNAMES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/usernames"
VIDEO = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video"
- VIEW_PROPS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProp"
- "s"
- )
- VML_DRAWING = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawi"
- "ng"
- )
+ VIEW_PROPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps"
+ VML_DRAWING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
VOLATILE_DEPENDENCIES = (
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatile"
"Dependencies"
)
- WEB_SETTINGS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSetti"
- "ngs"
- )
+ WEB_SETTINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings"
WORKSHEET_SOURCE = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/workshee"
- "tSource"
- )
- XML_MAPS = (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/xmlMaps"
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheetSource"
)
+ XML_MAPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/xmlMaps"
diff --git a/src/pptx/opc/oxml.py b/src/pptx/opc/oxml.py
index da774c3c9..5dd902a55 100644
--- a/src/pptx/opc/oxml.py
+++ b/src/pptx/opc/oxml.py
@@ -1,25 +1,30 @@
-# encoding: utf-8
-
"""OPC-local oxml module to handle OPC-local concerns like relationship parsing."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Callable, cast
+
from lxml import etree
-from .constants import NAMESPACE as NS, RELATIONSHIP_TARGET_MODE as RTM
-from ..oxml import parse_xml, register_element_cls
-from ..oxml.simpletypes import (
+from pptx.opc.constants import NAMESPACE as NS
+from pptx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM
+from pptx.oxml import parse_xml, register_element_cls
+from pptx.oxml.simpletypes import (
ST_ContentType,
ST_Extension,
ST_TargetMode,
XsdAnyUri,
XsdId,
)
-from ..oxml.xmlchemy import (
+from pptx.oxml.xmlchemy import (
BaseOxmlElement,
OptionalAttribute,
RequiredAttribute,
ZeroOrMore,
)
+if TYPE_CHECKING:
+ from pptx.opc.packuri import PackURI
nsmap = {
"ct": NS.OPC_CONTENT_TYPES,
@@ -28,58 +33,89 @@
}
-def oxml_tostring(elm, encoding=None, pretty_print=False, standalone=None):
+def oxml_to_encoded_bytes(
+ element: BaseOxmlElement,
+ encoding: str = "utf-8",
+ pretty_print: bool = False,
+ standalone: bool | None = None,
+) -> bytes:
return etree.tostring(
- elm, encoding=encoding, pretty_print=pretty_print, standalone=standalone
+ element, encoding=encoding, pretty_print=pretty_print, standalone=standalone
)
-def serialize_part_xml(part_elm):
- xml = etree.tostring(part_elm, encoding="UTF-8", standalone=True)
- return xml
+def oxml_tostring(
+ elm: BaseOxmlElement,
+ encoding: str | None = None,
+ pretty_print: bool = False,
+ standalone: bool | None = None,
+):
+ return etree.tostring(elm, encoding=encoding, pretty_print=pretty_print, standalone=standalone)
-class CT_Default(BaseOxmlElement):
+def serialize_part_xml(part_elm: BaseOxmlElement) -> bytes:
+ """Produce XML-file bytes for `part_elm`, suitable for writing directly to a `.xml` file.
+
+ Includes XML-declaration header.
"""
- ```` element, specifying the default content type to be applied
- to a part with the specified extension.
+ return etree.tostring(part_elm, encoding="UTF-8", standalone=True)
+
+
+class CT_Default(BaseOxmlElement):
+ """`` element.
+
+ Specifies the default content type to be applied to a part with the specified extension.
"""
- extension = RequiredAttribute("Extension", ST_Extension)
- contentType = RequiredAttribute("ContentType", ST_ContentType)
+ extension: str = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "Extension", ST_Extension
+ )
+ contentType: str = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "ContentType", ST_ContentType
+ )
class CT_Override(BaseOxmlElement):
- """
- ```` element, specifying the content type to be applied for a
- part with the specified partname.
+ """`` element.
+
+ Specifies the content type to be applied for a part with the specified partname.
"""
- partName = RequiredAttribute("PartName", XsdAnyUri)
- contentType = RequiredAttribute("ContentType", ST_ContentType)
+ partName: str = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "PartName", XsdAnyUri
+ )
+ contentType: str = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "ContentType", ST_ContentType
+ )
class CT_Relationship(BaseOxmlElement):
- """
- ```` element, representing a single relationship from a
- source to a target part.
+ """`` element.
+
+ Represents a single relationship from a source to a target part.
"""
- rId = RequiredAttribute("Id", XsdId)
- reltype = RequiredAttribute("Type", XsdAnyUri)
- target_ref = RequiredAttribute("Target", XsdAnyUri)
- targetMode = OptionalAttribute("TargetMode", ST_TargetMode, default=RTM.INTERNAL)
+ rId: str = RequiredAttribute("Id", XsdId) # pyright: ignore[reportAssignmentType]
+ reltype: str = RequiredAttribute("Type", XsdAnyUri) # pyright: ignore[reportAssignmentType]
+ target_ref: str = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "Target", XsdAnyUri
+ )
+ targetMode: str = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "TargetMode", ST_TargetMode, default=RTM.INTERNAL
+ )
@classmethod
- def new(cls, rId, reltype, target, target_mode=RTM.INTERNAL):
- """
- Return a new ```` element.
+ def new(
+ cls, rId: str, reltype: str, target_ref: str, target_mode: str = RTM.INTERNAL
+ ) -> CT_Relationship:
+ """Return a new `` element.
+
+ `target_ref` is either a partname or a URI.
"""
- xml = '' % nsmap["pr"]
- relationship = parse_xml(xml)
+ relationship = cast(CT_Relationship, parse_xml(f''))
relationship.rId = rId
relationship.reltype = reltype
- relationship.target_ref = target
+ relationship.target_ref = target_ref
relationship.targetMode = target_mode
return relationship
@@ -87,62 +123,61 @@ def new(cls, rId, reltype, target, target_mode=RTM.INTERNAL):
class CT_Relationships(BaseOxmlElement):
"""`` element, the root element in a .rels file."""
+ relationship_lst: list[CT_Relationship]
+ _insert_relationship: Callable[[CT_Relationship], CT_Relationship]
+
relationship = ZeroOrMore("pr:Relationship")
- def add_rel(self, rId, reltype, target, is_external=False):
- """
- Add a child ```` element with attributes set according
- to parameter values.
- """
+ def add_rel(
+ self, rId: str, reltype: str, target: str, is_external: bool = False
+ ) -> CT_Relationship:
+ """Add a child `` element with attributes set as specified."""
target_mode = RTM.EXTERNAL if is_external else RTM.INTERNAL
relationship = CT_Relationship.new(rId, reltype, target, target_mode)
- self._insert_relationship(relationship)
+ return self._insert_relationship(relationship)
@classmethod
- def new(cls):
- """Return a new ```` element."""
- return parse_xml('' % nsmap["pr"])
+ def new(cls) -> CT_Relationships:
+ """Return a new `` element."""
+ return cast(CT_Relationships, parse_xml(f''))
@property
- def xml(self):
- """
- Return XML string for this element, suitable for saving in a .rels
- stream, not pretty printed and with an XML declaration at the top.
+ def xml_file_bytes(self) -> bytes:
+ """Return XML bytes, with XML-declaration, for this `` element.
+
+ Suitable for saving in a .rels stream, not pretty printed and with an XML declaration at
+ the top.
"""
- return oxml_tostring(self, encoding="UTF-8", standalone=True)
+ return oxml_to_encoded_bytes(self, encoding="UTF-8", standalone=True)
class CT_Types(BaseOxmlElement):
+ """`` element.
+
+ The container element for Default and Override elements in [Content_Types].xml.
"""
- ```` element, the container element for Default and Override
- elements in [Content_Types].xml.
- """
+
+ default_lst: list[CT_Default]
+ override_lst: list[CT_Override]
+
+ _add_default: Callable[..., CT_Default]
+ _add_override: Callable[..., CT_Override]
default = ZeroOrMore("ct:Default")
override = ZeroOrMore("ct:Override")
- def add_default(self, ext, content_type):
- """
- Add a child ```` element with attributes set to parameter
- values.
- """
+ def add_default(self, ext: str, content_type: str) -> CT_Default:
+ """Add a child `` element with attributes set to parameter values."""
return self._add_default(extension=ext, contentType=content_type)
- def add_override(self, partname, content_type):
- """
- Add a child ```` element with attributes set to parameter
- values.
- """
+ def add_override(self, partname: PackURI, content_type: str) -> CT_Override:
+ """Add a child `` element with attributes set to parameter values."""
return self._add_override(partName=partname, contentType=content_type)
@classmethod
- def new(cls):
- """
- Return a new ```` element.
- """
- xml = '' % nsmap["ct"]
- types = parse_xml(xml)
- return types
+ def new(cls) -> CT_Types:
+ """Return a new `` element."""
+ return cast(CT_Types, parse_xml(f''))
register_element_cls("ct:Default", CT_Default)
diff --git a/src/pptx/opc/package.py b/src/pptx/opc/package.py
index 0427cf347..03ee5f43b 100644
--- a/src/pptx/opc/package.py
+++ b/src/pptx/opc/package.py
@@ -1,15 +1,17 @@
-# encoding: utf-8
-
"""Fundamental Open Packaging Convention (OPC) objects.
The :mod:`pptx.packaging` module coheres around the concerns of reading and writing
presentations to and from a .pptx file.
"""
+from __future__ import annotations
+
import collections
+from collections.abc import Mapping
+from typing import IO, TYPE_CHECKING, DefaultDict, Iterator, Set, cast
-from pptx.compat import is_string, Mapping
-from pptx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM, RELATIONSHIP_TYPE as RT
+from pptx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM
+from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.opc.oxml import CT_Relationships, serialize_part_xml
from pptx.opc.packuri import CONTENT_TYPES_URI, PACKAGE_URI, PackURI
from pptx.opc.serialized import PackageReader, PackageWriter
@@ -17,41 +19,49 @@
from pptx.oxml import parse_xml
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from typing_extensions import Self
+
+ from pptx.opc.oxml import CT_Relationship, CT_Types
+ from pptx.oxml.xmlchemy import BaseOxmlElement
+ from pptx.package import Package
+ from pptx.parts.presentation import PresentationPart
-class _RelatableMixin(object):
+
+class _RelatableMixin:
"""Provide relationship methods required by both the package and each part."""
- def part_related_by(self, reltype):
+ def part_related_by(self, reltype: str) -> Part:
"""Return (single) part having relationship to this package of `reltype`.
- Raises |KeyError| if no such relationship is found and |ValueError| if more than
- one such relationship is found.
+ Raises |KeyError| if no such relationship is found and |ValueError| if more than one such
+ relationship is found.
"""
return self._rels.part_with_reltype(reltype)
- def relate_to(self, target, reltype, is_external=False):
+ def relate_to(self, target: Part | str, reltype: str, is_external: bool = False) -> str:
"""Return rId key of relationship of `reltype` to `target`.
- If such a relationship already exists, its rId is returned. Otherwise the
- relationship is added and its new rId returned.
+ If such a relationship already exists, its rId is returned. Otherwise the relationship is
+ added and its new rId returned.
"""
- return (
- self._rels.get_or_add_ext_rel(reltype, target)
- if is_external
- else self._rels.get_or_add(reltype, target)
- )
+ if isinstance(target, str):
+ assert is_external
+ return self._rels.get_or_add_ext_rel(reltype, target)
+
+ return self._rels.get_or_add(reltype, target)
- def related_part(self, rId):
+ def related_part(self, rId: str) -> Part:
"""Return related |Part| subtype identified by `rId`."""
return self._rels[rId].target_part
- def target_ref(self, rId):
+ def target_ref(self, rId: str) -> str:
"""Return URL contained in target ref of relationship identified by `rId`."""
return self._rels[rId].target_ref
@lazyproperty
- def _rels(self):
- """|Relationships| object containing relationships from this part to others."""
+ def _rels(self) -> _Relationships:
+ """|_Relationships| object containing relationships from this part to others."""
raise NotImplementedError( # pragma: no cover
"`%s` must implement `.rels`" % type(self).__name__
)
@@ -60,25 +70,25 @@ def _rels(self):
class OpcPackage(_RelatableMixin):
"""Main API class for |python-opc|.
- A new instance is constructed by calling the :meth:`open` classmethod with a path
- to a package file or file-like object containing a package (.pptx file).
+ A new instance is constructed by calling the :meth:`open` classmethod with a path to a package
+ file or file-like object containing a package (.pptx file).
"""
- def __init__(self, pkg_file):
+ def __init__(self, pkg_file: str | IO[bytes]):
self._pkg_file = pkg_file
@classmethod
- def open(cls, pkg_file):
+ def open(cls, pkg_file: str | IO[bytes]) -> Self:
"""Return an |OpcPackage| instance loaded with the contents of `pkg_file`."""
return cls(pkg_file)._load()
- def drop_rel(self, rId):
+ def drop_rel(self, rId: str) -> None:
"""Remove relationship identified by `rId`."""
self._rels.pop(rId)
- def iter_parts(self):
+ def iter_parts(self) -> Iterator[Part]:
"""Generate exactly one reference to each part in the package."""
- visited = set()
+ visited: Set[Part] = set()
for rel in self.iter_rels():
if rel.is_external:
continue
@@ -88,114 +98,109 @@ def iter_parts(self):
yield part
visited.add(part)
- def iter_rels(self):
+ def iter_rels(self) -> Iterator[_Relationship]:
"""Generate exactly one reference to each relationship in package.
Performs a depth-first traversal of the rels graph.
"""
- visited = set()
+ visited: Set[Part] = set()
- def walk_rels(rels):
+ def walk_rels(rels: _Relationships) -> Iterator[_Relationship]:
for rel in rels.values():
yield rel
# --- external items can have no relationships ---
if rel.is_external:
continue
- # --- all relationships other than those for the package belong to a
- # --- part. Once that part has been processed, processing it again
- # --- would lead to the same relationships appearing more than once.
+ # -- all relationships other than those for the package belong to a part. Once
+ # -- that part has been processed, processing it again would lead to the same
+ # -- relationships appearing more than once.
part = rel.target_part
if part in visited:
continue
visited.add(part)
# --- recurse into relationships of each unvisited target-part ---
- for rel in walk_rels(part.rels):
- yield rel
+ yield from walk_rels(part.rels)
- for rel in walk_rels(self._rels):
- yield rel
+ yield from walk_rels(self._rels)
@property
- def main_document_part(self):
+ def main_document_part(self) -> PresentationPart:
"""Return |Part| subtype serving as the main document part for this package.
In this case it will be a |Presentation| part.
"""
- return self.part_related_by(RT.OFFICE_DOCUMENT)
+ return cast("PresentationPart", self.part_related_by(RT.OFFICE_DOCUMENT))
- def next_partname(self, tmpl):
+ def next_partname(self, tmpl: str) -> PackURI:
"""Return |PackURI| next available partname matching `tmpl`.
- `tmpl` is a printf (%)-style template string containing a single replacement
- item, a '%d' to be used to insert the integer portion of the partname.
- Example: '/ppt/slides/slide%d.xml'
+ `tmpl` is a printf (%)-style template string containing a single replacement item, a '%d'
+ to be used to insert the integer portion of the partname. Example:
+ '/ppt/slides/slide%d.xml'
"""
# --- expected next partname is tmpl % n where n is one greater than the number
# --- of existing partnames that match tmpl. Speed up finding the next one
# --- (maybe) by searching from the end downward rather than from 1 upward.
prefix = tmpl[: (tmpl % 42).find("42")]
- partnames = set(
- p.partname for p in self.iter_parts() if p.partname.startswith(prefix)
- )
+ partnames = {p.partname for p in self.iter_parts() if p.partname.startswith(prefix)}
for n in range(len(partnames) + 1, 0, -1):
candidate_partname = tmpl % n
if candidate_partname not in partnames:
return PackURI(candidate_partname)
- raise Exception( # pragma: no cover
- "ProgrammingError: ran out of candidate_partnames"
- )
+ raise Exception("ProgrammingError: ran out of candidate_partnames") # pragma: no cover
- def save(self, pkg_file):
+ def save(self, pkg_file: str | IO[bytes]) -> None:
"""Save this package to `pkg_file`.
`file` can be either a path to a file (a string) or a file-like object.
"""
PackageWriter.write(pkg_file, self._rels, tuple(self.iter_parts()))
- def _load(self):
+ def _load(self) -> Self:
"""Return the package after loading all parts and relationships."""
- pkg_xml_rels, parts = _PackageLoader.load(self._pkg_file, self)
+ pkg_xml_rels, parts = _PackageLoader.load(self._pkg_file, cast("Package", self))
self._rels.load_from_xml(PACKAGE_URI, pkg_xml_rels, parts)
return self
@lazyproperty
- def _rels(self):
+ def _rels(self) -> _Relationships:
"""|Relationships| object containing relationships of this package."""
return _Relationships(PACKAGE_URI.baseURI)
-class _PackageLoader(object):
+class _PackageLoader:
"""Function-object that loads a package from disk (or other store)."""
- def __init__(self, pkg_file, package):
+ def __init__(self, pkg_file: str | IO[bytes], package: Package):
self._pkg_file = pkg_file
self._package = package
@classmethod
- def load(cls, pkg_file, package):
+ def load(
+ cls, pkg_file: str | IO[bytes], package: Package
+ ) -> tuple[CT_Relationships, dict[PackURI, Part]]:
"""Return (pkg_xml_rels, parts) pair resulting from loading `pkg_file`.
- The returned `parts` value is a {partname: part} mapping with each part in the
- package included and constructed complete with its relationships to other parts
- in the package.
+ The returned `parts` value is a {partname: part} mapping with each part in the package
+ included and constructed complete with its relationships to other parts in the package.
- The returned `pkg_xml_rels` value is a `CT_Relationships` object containing the
- parsed package relationships. It is the caller's responsibility (the package
- object) to load those relationships into its |_Relationships| object.
+ The returned `pkg_xml_rels` value is a `CT_Relationships` object containing the parsed
+ package relationships. It is the caller's responsibility (the package object) to load
+ those relationships into its |_Relationships| object.
"""
return cls(pkg_file, package)._load()
- def _load(self):
+ def _load(self) -> tuple[CT_Relationships, dict[PackURI, Part]]:
"""Return (pkg_xml_rels, parts) pair resulting from loading pkg_file."""
parts, xml_rels = self._parts, self._xml_rels
for partname, part in parts.items():
part.load_rels_from_xml(xml_rels[partname], parts)
- return xml_rels["/"], parts
+ return xml_rels[PACKAGE_URI], parts
@lazyproperty
- def _content_types(self):
+ def _content_types(self) -> _ContentTypeMap:
"""|_ContentTypeMap| object providing content-types for items of this package.
Provides a content-type (MIME-type) for any given partname.
@@ -203,18 +208,17 @@ def _content_types(self):
return _ContentTypeMap.from_xml(self._package_reader[CONTENT_TYPES_URI])
@lazyproperty
- def _package_reader(self):
+ def _package_reader(self) -> PackageReader:
"""|PackageReader| object providing access to package-items in pkg_file."""
return PackageReader(self._pkg_file)
@lazyproperty
- def _parts(self):
+ def _parts(self) -> dict[PackURI, Part]:
"""dict {partname: Part} populated with parts loading from package.
- Among other duties, this collection is passed to each relationships collection
- so each relationship can resolve a reference to its target part when required.
- This reference can only be reliably carried out once the all parts have been
- loaded.
+ Among other duties, this collection is passed to each relationships collection so each
+ relationship can resolve a reference to its target part when required. This reference can
+ only be reliably carried out once the all parts have been loaded.
"""
content_types = self._content_types
package = self._package
@@ -227,30 +231,30 @@ def _parts(self):
package,
blob=package_reader[partname],
)
- for partname in (p for p in self._xml_rels.keys() if p != "/")
- # --- invalid partnames can arise in some packages; ignore those rather
- # --- than raise an exception.
+ for partname in (p for p in self._xml_rels if p != "/")
+ # -- invalid partnames can arise in some packages; ignore those rather than raise an
+ # -- exception.
if partname in package_reader
}
@lazyproperty
- def _xml_rels(self):
+ def _xml_rels(self) -> dict[PackURI, CT_Relationships]:
"""dict {partname: xml_rels} for package and all package parts.
This is used as the basis for other loading operations such as loading parts and
populating their relationships.
"""
- xml_rels = {}
- visited_partnames = set()
+ xml_rels: dict[PackURI, CT_Relationships] = {}
+ visited_partnames: Set[PackURI] = set()
- def load_rels(source_partname, rels):
+ def load_rels(source_partname: PackURI, rels: CT_Relationships):
"""Populate `xml_rels` dict by traversing relationships depth-first."""
xml_rels[source_partname] = rels
visited_partnames.add(source_partname)
base_uri = source_partname.baseURI
# --- recursion stops when there are no unvisited partnames in rels ---
- for rel in rels:
+ for rel in rels.relationship_lst:
if rel.targetMode == RTM.EXTERNAL:
continue
target_partname = PackURI.from_rel_ref(base_uri, rel.target_ref)
@@ -261,26 +265,31 @@ def load_rels(source_partname, rels):
load_rels(PACKAGE_URI, self._xml_rels_for(PACKAGE_URI))
return xml_rels
- def _xml_rels_for(self, partname):
+ def _xml_rels_for(self, partname: PackURI) -> CT_Relationships:
"""Return CT_Relationships object formed by parsing rels XML for `partname`.
- A CT_Relationships object is returned in all cases. A part that has no
- relationships receives an "empty" CT_Relationships object, i.e. containing no
- `CT_Relationship` objects.
+ A CT_Relationships object is returned in all cases. A part that has no relationships
+ receives an "empty" CT_Relationships object, i.e. containing no `CT_Relationship` objects.
"""
rels_xml = self._package_reader.rels_xml_for(partname)
- return CT_Relationships.new() if rels_xml is None else parse_xml(rels_xml)
+ return (
+ CT_Relationships.new()
+ if rels_xml is None
+ else cast(CT_Relationships, parse_xml(rels_xml))
+ )
class Part(_RelatableMixin):
"""Base class for package parts.
- Provides common properties and methods, but intended to be subclassed in client code
- to implement specific part behaviors. Also serves as the default class for parts
- that are not yet given specific behaviors.
+ Provides common properties and methods, but intended to be subclassed in client code to
+ implement specific part behaviors. Also serves as the default class for parts that are not yet
+ given specific behaviors.
"""
- def __init__(self, partname, content_type, package, blob=None):
+ def __init__(
+ self, partname: PackURI, content_type: str, package: Package, blob: bytes | None = None
+ ):
# --- XmlPart subtypes, don't store a blob (the original XML) ---
self._partname = partname
self._content_type = content_type
@@ -288,86 +297,74 @@ def __init__(self, partname, content_type, package, blob=None):
self._blob = blob
@classmethod
- def load(cls, partname, content_type, package, blob):
+ def load(cls, partname: PackURI, content_type: str, package: Package, blob: bytes) -> Self:
"""Return `cls` instance loaded from arguments.
- This one is a straight pass-through, but subtypes may do some pre-processing,
- see XmlPart for an example.
+ This one is a straight pass-through, but subtypes may do some pre-processing, see XmlPart
+ for an example.
"""
return cls(partname, content_type, package, blob)
@property
- def blob(self):
+ def blob(self) -> bytes:
"""Contents of this package part as a sequence of bytes.
- May be text (XML generally) or binary. Intended to be overridden by subclasses.
- Default behavior is to return the blob initial loaded during `Package.open()`
- operation.
+ Intended to be overridden by subclasses. Default behavior is to return the blob initial
+ loaded during `Package.open()` operation.
"""
- return self._blob
+ return self._blob or b""
@blob.setter
- def blob(self, bytes_):
+ def blob(self, blob: bytes):
"""Note that not all subclasses use the part blob as their blob source.
- In particular, the |XmlPart| subclass uses its `self._element` to serialize a
- blob on demand. This works fine for binary parts though.
+ In particular, the |XmlPart| subclass uses its `self._element` to serialize a blob on
+ demand. This works fine for binary parts though.
"""
- self._blob = bytes_
+ self._blob = blob
@lazyproperty
- def content_type(self):
+ def content_type(self) -> str:
"""Content-type (MIME-type) of this part."""
return self._content_type
- def drop_rel(self, rId):
- """Remove relationship identified by `rId` if its reference count is under 2.
-
- Relationships with a reference count of 0 are implicit relationships. Note that
- only XML parts can drop relationships.
- """
- if self._rel_ref_count(rId) < 2:
- self._rels.pop(rId)
-
- def load_rels_from_xml(self, xml_rels, parts):
+ def load_rels_from_xml(self, xml_rels: CT_Relationships, parts: dict[PackURI, Part]) -> None:
"""load _Relationships for this part from `xml_rels`.
- Part references are resolved using the `parts` dict that maps each partname to
- the loaded part with that partname. These relationships are loaded from a
- serialized package and so already have assigned rIds. This method is only used
- during package loading.
+ Part references are resolved using the `parts` dict that maps each partname to the loaded
+ part with that partname. These relationships are loaded from a serialized package and so
+ already have assigned rIds. This method is only used during package loading.
"""
self._rels.load_from_xml(self._partname.baseURI, xml_rels, parts)
@lazyproperty
- def package(self):
- """|OpcPackage| instance this part belongs to."""
+ def package(self) -> Package:
+ """Package this part belongs to."""
return self._package
@property
- def partname(self):
+ def partname(self) -> PackURI:
"""|PackURI| partname for this part, e.g. "/ppt/slides/slide1.xml"."""
return self._partname
@partname.setter
- def partname(self, partname):
- if not isinstance(partname, PackURI):
+ def partname(self, partname: PackURI):
+ if not isinstance(partname, PackURI): # pyright: ignore[reportUnnecessaryIsInstance]
raise TypeError( # pragma: no cover
- "partname must be instance of PackURI, got '%s'"
- % type(partname).__name__
+ "partname must be instance of PackURI, got '%s'" % type(partname).__name__
)
self._partname = partname
@lazyproperty
- def rels(self):
- """|Relationships| collection of relationships from this part to other parts."""
+ def rels(self) -> _Relationships:
+ """Collection of relationships from this part to other parts."""
# --- this must be public to allow the part graph to be traversed ---
return self._rels
- def _blob_from_file(self, file):
+ def _blob_from_file(self, file: str | IO[bytes]) -> bytes:
"""Return bytes of `file`, which is either a str path or a file-like object."""
# --- a str `file` is assumed to be a path ---
- if is_string(file):
+ if isinstance(file, str):
with open(file, "rb") as f:
return f.read()
@@ -377,13 +374,9 @@ def _blob_from_file(self, file):
file.seek(0)
return file.read()
- def _rel_ref_count(self, rId):
- """Return int count of references in this part's XML to `rId`."""
- return len([r for r in self._element.xpath("//@r:id") if r == rId])
-
@lazyproperty
- def _rels(self):
- """|Relationships| object containing relationships from this part to others."""
+ def _rels(self) -> _Relationships:
+ """Relationships from this part to others."""
return _Relationships(self._partname.baseURI)
@@ -394,46 +387,65 @@ class XmlPart(Part):
reserializing the XML payload and managing relationships to other parts.
"""
- def __init__(self, partname, content_type, package, element):
+ def __init__(
+ self, partname: PackURI, content_type: str, package: Package, element: BaseOxmlElement
+ ):
super(XmlPart, self).__init__(partname, content_type, package)
self._element = element
@classmethod
- def load(cls, partname, content_type, package, blob):
+ def load(cls, partname: PackURI, content_type: str, package: Package, blob: bytes):
"""Return instance of `cls` loaded with parsed XML from `blob`."""
- return cls(partname, content_type, package, element=parse_xml(blob))
+ return cls(
+ partname, content_type, package, element=cast("BaseOxmlElement", parse_xml(blob))
+ )
@property
- def blob(self):
+ def blob(self) -> bytes: # pyright: ignore[reportIncompatibleMethodOverride]
"""bytes XML serialization of this part."""
return serialize_part_xml(self._element)
+ # -- XmlPart cannot set its blob, which is why pyright complains --
+
+ def drop_rel(self, rId: str) -> None:
+ """Remove relationship identified by `rId` if its reference count is under 2.
+
+ Relationships with a reference count of 0 are implicit relationships. Note that only XML
+ parts can drop relationships.
+ """
+ if self._rel_ref_count(rId) < 2:
+ self._rels.pop(rId)
+
@property
def part(self):
"""This part.
- This is part of the parent protocol, "children" of the document will not know
- the part that contains them so must ask their parent object. That chain of
- delegation ends here for child objects.
+ This is part of the parent protocol, "children" of the document will not know the part
+ that contains them so must ask their parent object. That chain of delegation ends here for
+ child objects.
"""
return self
+ def _rel_ref_count(self, rId: str) -> int:
+ """Return int count of references in this part's XML to `rId`."""
+ return len([r for r in cast(list[str], self._element.xpath("//@r:id")) if r == rId])
+
-class PartFactory(object):
+class PartFactory:
"""Constructs a registered subtype of |Part|.
- Client code can register a subclass of |Part| to be used for a package blob based on
- its content type.
+ Client code can register a subclass of |Part| to be used for a package blob based on its
+ content type.
"""
- part_type_for = {}
+ part_type_for: dict[str, type[Part]] = {}
- def __new__(cls, partname, content_type, package, blob):
+ def __new__(cls, partname: PackURI, content_type: str, package: Package, blob: bytes) -> Part:
PartClass = cls._part_cls_for(content_type)
return PartClass.load(partname, content_type, package, blob)
@classmethod
- def _part_cls_for(cls, content_type):
+ def _part_cls_for(cls, content_type: str) -> type[Part]:
"""Return the custom part class registered for `content_type`.
Returns |Part| if no custom class is registered for `content_type`.
@@ -443,19 +455,18 @@ def _part_cls_for(cls, content_type):
return Part
-class _ContentTypeMap(object):
+class _ContentTypeMap:
"""Value type providing dict semantics for looking up content type by partname."""
- def __init__(self, overrides, defaults):
+ def __init__(self, overrides: dict[str, str], defaults: dict[str, str]):
self._overrides = overrides
self._defaults = defaults
- def __getitem__(self, partname):
+ def __getitem__(self, partname: PackURI) -> str:
"""Return content-type (MIME-type) for part identified by *partname*."""
- if not isinstance(partname, PackURI):
+ if not isinstance(partname, PackURI): # pyright: ignore[reportUnnecessaryIsInstance]
raise TypeError(
- "_ContentTypeMap key must be , got %s"
- % type(partname).__name__
+ "_ContentTypeMap key must be , got %s" % type(partname).__name__
)
if partname in self._overrides:
@@ -464,14 +475,13 @@ def __getitem__(self, partname):
if partname.ext in self._defaults:
return self._defaults[partname.ext]
- raise KeyError(
- "no content-type for partname '%s' in [Content_Types].xml" % partname
- )
+ raise KeyError("no content-type for partname '%s' in [Content_Types].xml" % partname)
@classmethod
- def from_xml(cls, content_types_xml):
+ def from_xml(cls, content_types_xml: bytes) -> _ContentTypeMap:
"""Return |_ContentTypeMap| instance populated from `content_types_xml`."""
- types_elm = parse_xml(content_types_xml)
+ types_elm = cast("CT_Types", parse_xml(content_types_xml))
+ # -- note all partnames in [Content_Types].xml are absolute --
overrides = CaseInsensitiveDict(
(o.partName.lower(), o.contentType) for o in types_elm.override_lst
)
@@ -481,57 +491,55 @@ def from_xml(cls, content_types_xml):
return cls(overrides, defaults)
-class _Relationships(Mapping):
+class _Relationships(Mapping[str, "_Relationship"]):
"""Collection of |_Relationship| instances having `dict` semantics.
- Relationships are keyed by their rId, but may also be found in other ways, such as
- by their relationship type. |Relationship| objects are keyed by their rId.
+ Relationships are keyed by their rId, but may also be found in other ways, such as by their
+ relationship type. |Relationship| objects are keyed by their rId.
- Iterating this collection has normal mapping semantics, generating the keys (rIds)
- of the mapping. `rels.keys()`, `rels.values()`, and `rels.items() can be used as
- they would be for a `dict`.
+ Iterating this collection has normal mapping semantics, generating the keys (rIds) of the
+ mapping. `rels.keys()`, `rels.values()`, and `rels.items() can be used as they would be for a
+ `dict`.
"""
- def __init__(self, base_uri):
+ def __init__(self, base_uri: str):
self._base_uri = base_uri
- def __contains__(self, rId):
+ def __contains__(self, rId: object) -> bool:
"""Implement 'in' operation, like `"rId7" in relationships`."""
return rId in self._rels
- def __getitem__(self, rId):
+ def __getitem__(self, rId: str) -> _Relationship:
"""Implement relationship lookup by rId using indexed access, like rels[rId]."""
try:
return self._rels[rId]
except KeyError:
raise KeyError("no relationship with key '%s'" % rId)
- def __iter__(self):
+ def __iter__(self) -> Iterator[str]:
"""Implement iteration of rIds (iterating a mapping produces its keys)."""
return iter(self._rels)
- def __len__(self):
+ def __len__(self) -> int:
"""Return count of relationships in collection."""
return len(self._rels)
- def get_or_add(self, reltype, target_part):
+ def get_or_add(self, reltype: str, target_part: Part) -> str:
"""Return str rId of `reltype` to `target_part`.
- The rId of an existing matching relationship is used if present. Otherwise, a
- new relationship is added and that rId is returned.
+ The rId of an existing matching relationship is used if present. Otherwise, a new
+ relationship is added and that rId is returned.
"""
existing_rId = self._get_matching(reltype, target_part)
return (
- self._add_relationship(reltype, target_part)
- if existing_rId is None
- else existing_rId
+ self._add_relationship(reltype, target_part) if existing_rId is None else existing_rId
)
- def get_or_add_ext_rel(self, reltype, target_ref):
+ def get_or_add_ext_rel(self, reltype: str, target_ref: str) -> str:
"""Return str rId of external relationship of `reltype` to `target_ref`.
- The rId of an existing matching relationship is used if present. Otherwise, a
- new relationship is added and that rId is returned.
+ The rId of an existing matching relationship is used if present. Otherwise, a new
+ relationship is added and that rId is returned.
"""
existing_rId = self._get_matching(reltype, target_ref, is_external=True)
return (
@@ -540,7 +548,9 @@ def get_or_add_ext_rel(self, reltype, target_ref):
else existing_rId
)
- def load_from_xml(self, base_uri, xml_rels, parts):
+ def load_from_xml(
+ self, base_uri: str, xml_rels: CT_Relationships, parts: dict[PackURI, Part]
+ ) -> None:
"""Replace any relationships in this collection with those from `xml_rels`."""
def iter_valid_rels():
@@ -559,11 +569,11 @@ def iter_valid_rels():
self._rels.clear()
self._rels.update((rel.rId, rel) for rel in iter_valid_rels())
- def part_with_reltype(self, reltype):
+ def part_with_reltype(self, reltype: str) -> Part:
"""Return target part of relationship with matching `reltype`.
- Raises |KeyError| if not found and |ValueError| if more than one matching
- relationship is found.
+ Raises |KeyError| if not found and |ValueError| if more than one matching relationship is
+ found.
"""
rels_of_reltype = self._rels_by_reltype[reltype]
@@ -571,14 +581,12 @@ def part_with_reltype(self, reltype):
raise KeyError("no relationship of type '%s' in collection" % reltype)
if len(rels_of_reltype) > 1:
- raise ValueError(
- "multiple relationships of type '%s' in collection" % reltype
- )
+ raise ValueError("multiple relationships of type '%s' in collection" % reltype)
return rels_of_reltype[0].target_part
- def pop(self, rId):
- """Return |Relationship| identified by `rId` after removing it from collection.
+ def pop(self, rId: str) -> _Relationship:
+ """Return |_Relationship| identified by `rId` after removing it from collection.
The caller is responsible for ensuring it is no longer required.
"""
@@ -588,8 +596,8 @@ def pop(self, rId):
def xml(self):
"""bytes XML serialization of this relationship collection.
- This value is suitable for storage as a .rels file in an OPC package. Includes
- a ` str:
"""Return str rId of |_Relationship| newly added to spec."""
rId = self._next_rId
self._rels[rId] = _Relationship(
@@ -622,7 +630,9 @@ def _add_relationship(self, reltype, target, is_external=False):
)
return rId
- def _get_matching(self, reltype, target, is_external=False):
+ def _get_matching(
+ self, reltype: str, target: Part | str, is_external: bool = False
+ ) -> str | None:
"""Return optional str rId of rel of `reltype`, `target`, and `is_external`.
Returns `None` on no matching relationship
@@ -631,18 +641,17 @@ def _get_matching(self, reltype, target, is_external=False):
if rel.is_external != is_external:
continue
rel_target = rel.target_ref if rel.is_external else rel.target_part
- if rel_target != target:
- continue
- return rel.rId
+ if rel_target == target:
+ return rel.rId
return None
@property
- def _next_rId(self):
+ def _next_rId(self) -> str:
"""Next str rId available in collection.
- The next rId is the first unused key starting from "rId1" and making use of any
- gaps in numbering, e.g. 'rId2' for rIds ['rId1', 'rId3'].
+ The next rId is the first unused key starting from "rId1" and making use of any gaps in
+ numbering, e.g. 'rId2' for rIds ['rId1', 'rId3'].
"""
# --- The common case is where all sequential numbers starting at "rId1" are
# --- used and the next available rId is "rId%d" % (len(rels)+1). So we start
@@ -651,25 +660,28 @@ def _next_rId(self):
rId_candidate = "rId%d" % n # like 'rId19'
if rId_candidate not in self._rels:
return rId_candidate
+ raise Exception(
+ "ProgrammingError: Impossible to have more distinct rIds than relationships"
+ )
@lazyproperty
- def _rels(self):
+ def _rels(self) -> dict[str, _Relationship]:
"""dict {rId: _Relationship} containing relationships of this collection."""
- return dict()
+ return {}
@property
- def _rels_by_reltype(self):
+ def _rels_by_reltype(self) -> dict[str, list[_Relationship]]:
"""defaultdict {reltype: [rels]} for all relationships in collection."""
- D = collections.defaultdict(list)
+ D: DefaultDict[str, list[_Relationship]] = collections.defaultdict(list)
for rel in self.values():
D[rel.reltype].append(rel)
return D
-class _Relationship(object):
+class _Relationship:
"""Value object describing link from a part or package to another part."""
- def __init__(self, base_uri, rId, reltype, target_mode, target):
+ def __init__(self, base_uri: str, rId: str, reltype: str, target_mode: str, target: Part | str):
self._base_uri = base_uri
self._rId = rId
self._reltype = reltype
@@ -677,7 +689,9 @@ def __init__(self, base_uri, rId, reltype, target_mode, target):
self._target = target
@classmethod
- def from_xml(cls, base_uri, rel, parts):
+ def from_xml(
+ cls, base_uri: str, rel: CT_Relationship, parts: dict[PackURI, Part]
+ ) -> _Relationship:
"""Return |_Relationship| object based on CT_Relationship element `rel`."""
target = (
rel.target_ref
@@ -687,62 +701,63 @@ def from_xml(cls, base_uri, rel, parts):
return cls(base_uri, rel.rId, rel.reltype, rel.targetMode, target)
@lazyproperty
- def is_external(self):
+ def is_external(self) -> bool:
"""True if target_mode is `RTM.EXTERNAL`.
- An external relationship is a link to a resource outside the package, such as
- a web-resource (URL).
+ An external relationship is a link to a resource outside the package, such as a
+ web-resource (URL).
"""
return self._target_mode == RTM.EXTERNAL
@lazyproperty
- def reltype(self):
+ def reltype(self) -> str:
"""Member of RELATIONSHIP_TYPE describing relationship of target to source."""
return self._reltype
@lazyproperty
- def rId(self):
+ def rId(self) -> str:
"""str relationship-id, like 'rId9'.
- Corresponds to the `Id` attribute on the `CT_Relationship` element and
- uniquely identifies this relationship within its peers for the source-part or
- package.
+ Corresponds to the `Id` attribute on the `CT_Relationship` element and uniquely identifies
+ this relationship within its peers for the source-part or package.
"""
return self._rId
@lazyproperty
- def target_part(self):
+ def target_part(self) -> Part:
"""|Part| or subtype referred to by this relationship."""
if self.is_external:
raise ValueError(
"`.target_part` property on _Relationship is undefined when "
"target-mode is external"
)
+ assert isinstance(self._target, Part)
return self._target
@lazyproperty
- def target_partname(self):
+ def target_partname(self) -> PackURI:
"""|PackURI| instance containing partname targeted by this relationship.
- Raises `ValueError` on reference if target_mode is external. Use
- :attr:`target_mode` to check before referencing.
+ Raises `ValueError` on reference if target_mode is external. Use :attr:`target_mode` to
+ check before referencing.
"""
if self.is_external:
raise ValueError(
"`.target_partname` property on _Relationship is undefined when "
"target-mode is external"
)
+ assert isinstance(self._target, Part)
return self._target.partname
@lazyproperty
- def target_ref(self):
+ def target_ref(self) -> str:
"""str reference to relationship target.
- For internal relationships this is the relative partname, suitable for
- serialization purposes. For an external relationship it is typically a URL.
+ For internal relationships this is the relative partname, suitable for serialization
+ purposes. For an external relationship it is typically a URL.
"""
- return (
- self._target
- if self.is_external
- else self.target_partname.relative_ref(self._base_uri)
- )
+ if self.is_external:
+ assert isinstance(self._target, str)
+ return self._target
+
+ return self.target_partname.relative_ref(self._base_uri)
diff --git a/src/pptx/opc/packuri.py b/src/pptx/opc/packuri.py
index 65a0b44ab..74ddd333f 100644
--- a/src/pptx/opc/packuri.py
+++ b/src/pptx/opc/packuri.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Provides the PackURI value type and known pack-URI strings such as PACKAGE_URI."""
+from __future__ import annotations
+
import posixpath
import re
@@ -9,62 +9,59 @@
class PackURI(str):
"""Proxy for a pack URI (partname).
- Provides utility properties the baseURI and the filename slice. Behaves as |str|
- otherwise.
+ Provides utility properties the baseURI and the filename slice. Behaves as |str| otherwise.
"""
_filename_re = re.compile("([a-zA-Z]+)([0-9][0-9]*)?")
- def __new__(cls, pack_uri_str):
+ def __new__(cls, pack_uri_str: str):
if not pack_uri_str[0] == "/":
- raise ValueError("PackURI must begin with slash, got '%s'" % pack_uri_str)
+ raise ValueError(f"PackURI must begin with slash, got {repr(pack_uri_str)}")
return str.__new__(cls, pack_uri_str)
@staticmethod
- def from_rel_ref(baseURI, relative_ref):
- """
- Return a |PackURI| instance containing the absolute pack URI formed by
- translating *relative_ref* onto *baseURI*.
- """
+ def from_rel_ref(baseURI: str, relative_ref: str) -> PackURI:
+ """Construct an absolute pack URI formed by translating `relative_ref` onto `baseURI`."""
joined_uri = posixpath.join(baseURI, relative_ref)
abs_uri = posixpath.abspath(joined_uri)
return PackURI(abs_uri)
@property
- def baseURI(self):
- """
- The base URI of this pack URI, the directory portion, roughly
- speaking. E.g. ``'/ppt/slides'`` for ``'/ppt/slides/slide1.xml'``.
- For the package pseudo-partname '/', baseURI is '/'.
+ def baseURI(self) -> str:
+ """The base URI of this pack URI; the directory portion, roughly speaking.
+
+ E.g. `"/ppt/slides"` for `"/ppt/slides/slide1.xml"`.
+
+ For the package pseudo-partname "/", the baseURI is "/".
"""
return posixpath.split(self)[0]
@property
- def ext(self):
- """
- The extension portion of this pack URI, e.g. ``'xml'`` for
- ``'/ppt/slides/slide1.xml'``. Note that the period is not included.
+ def ext(self) -> str:
+ """The extension portion of this pack URI.
+
+ E.g. `"xml"` for `"/ppt/slides/slide1.xml"`. Note the leading period is not included.
"""
- # raw_ext is either empty string or starts with period, e.g. '.xml'
+ # -- raw_ext is either empty string or starts with period, e.g. ".xml" --
raw_ext = posixpath.splitext(self)[1]
return raw_ext[1:] if raw_ext.startswith(".") else raw_ext
@property
- def filename(self):
- """
- The "filename" portion of this pack URI, e.g. ``'slide1.xml'`` for
- ``'/ppt/slides/slide1.xml'``. For the package pseudo-partname '/',
- filename is ''.
+ def filename(self) -> str:
+ """The "filename" portion of this pack URI.
+
+ E.g. `"slide1.xml"` for `"/ppt/slides/slide1.xml"`.
+
+ For the package pseudo-partname "/", `filename` is ''.
"""
return posixpath.split(self)[1]
@property
- def idx(self):
+ def idx(self) -> int | None:
"""Optional int partname index.
- Value is an integer for an "array" partname or None for singleton partname, e.g.
- ``21`` for ``'/ppt/slides/slide21.xml'`` and |None| for
- ``'/ppt/presentation.xml'``.
+ Value is an integer for an "array" partname or None for singleton partname, e.g. `21` for
+ `"/ppt/slides/slide21.xml"` and |None| for `"/ppt/presentation.xml"`.
"""
filename = self.filename
if not filename:
@@ -78,34 +75,30 @@ def idx(self):
return None
@property
- def membername(self):
- """
- The pack URI with the leading slash stripped off, the form used as
- the Zip file membername for the package item. Returns '' for the
- package pseudo-partname '/'.
+ def membername(self) -> str:
+ """The pack URI with the leading slash stripped off.
+
+ This is the form used as the Zip file membername for the package item. Returns "" for the
+ package pseudo-partname "/".
"""
return self[1:]
- def relative_ref(self, baseURI):
- """
- Return string containing relative reference to package item from
- *baseURI*. E.g. PackURI('/ppt/slideLayouts/slideLayout1.xml') would
- return '../slideLayouts/slideLayout1.xml' for baseURI '/ppt/slides'.
+ def relative_ref(self, baseURI: str) -> str:
+ """Return string containing relative reference to package item from `baseURI`.
+
+ E.g. PackURI("/ppt/slideLayouts/slideLayout1.xml") would return
+ "../slideLayouts/slideLayout1.xml" for baseURI "/ppt/slides".
"""
# workaround for posixpath bug in 2.6, doesn't generate correct
- # relative path when *start* (second) parameter is root ('/')
- if baseURI == "/":
- relpath = self[1:]
- else:
- relpath = posixpath.relpath(self, baseURI)
- return relpath
+ # relative path when `start` (second) parameter is root ("/")
+ return self[1:] if baseURI == "/" else posixpath.relpath(self, baseURI)
@property
- def rels_uri(self):
- """
- The pack URI of the .rels part corresponding to the current pack URI.
- Only produces sensible output if the pack URI is a partname or the
- package pseudo-partname '/'.
+ def rels_uri(self) -> PackURI:
+ """The pack URI of the .rels part corresponding to the current pack URI.
+
+ Only produces sensible output if the pack URI is a partname or the package pseudo-partname
+ "/".
"""
rels_filename = "%s.rels" % self.filename
rels_uri_str = posixpath.join(self.baseURI, "_rels", rels_filename)
diff --git a/src/pptx/opc/serialized.py b/src/pptx/opc/serialized.py
index 9e6ad51e0..eba628247 100644
--- a/src/pptx/opc/serialized.py
+++ b/src/pptx/opc/serialized.py
@@ -1,13 +1,14 @@
-# encoding: utf-8
-
"""API for reading/writing serialized Open Packaging Convention (OPC) package."""
+from __future__ import annotations
+
import os
import posixpath
import zipfile
+from collections.abc import Container
+from typing import IO, TYPE_CHECKING, Any, Sequence
-from pptx.compat import Container, is_string
-from pptx.exceptions import PackageNotFoundError
+from pptx.exc import PackageNotFoundError
from pptx.opc.constants import CONTENT_TYPE as CT
from pptx.opc.oxml import CT_Types, serialize_part_xml
from pptx.opc.packuri import CONTENT_TYPES_URI, PACKAGE_URI, PackURI
@@ -15,114 +16,123 @@
from pptx.opc.spec import default_content_types
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.opc.package import Part, _Relationships # pyright: ignore[reportPrivateUsage]
+
-class PackageReader(Container):
+class PackageReader(Container[bytes]):
"""Provides access to package-parts of an OPC package with dict semantics.
- The package may be in zip-format (a .pptx file) or expanded into a directory
- structure, perhaps by unzipping a .pptx file.
+ The package may be in zip-format (a .pptx file) or expanded into a directory structure,
+ perhaps by unzipping a .pptx file.
"""
- def __init__(self, pkg_file):
+ def __init__(self, pkg_file: str | IO[bytes]):
self._pkg_file = pkg_file
- def __contains__(self, pack_uri):
+ def __contains__(self, pack_uri: object) -> bool:
"""Return True when part identified by `pack_uri` is present in package."""
return pack_uri in self._blob_reader
- def __getitem__(self, pack_uri):
+ def __getitem__(self, pack_uri: PackURI) -> bytes:
"""Return bytes for part corresponding to `pack_uri`."""
return self._blob_reader[pack_uri]
- def rels_xml_for(self, partname):
+ def rels_xml_for(self, partname: PackURI) -> bytes | None:
"""Return optional rels item XML for `partname`.
- Returns `None` if no rels item is present for `partname`. `partname` is a
- |PackURI| instance.
+ Returns `None` if no rels item is present for `partname`. `partname` is a |PackURI|
+ instance.
"""
blob_reader, uri = self._blob_reader, partname.rels_uri
return blob_reader[uri] if uri in blob_reader else None
@lazyproperty
- def _blob_reader(self):
+ def _blob_reader(self) -> _PhysPkgReader:
"""|_PhysPkgReader| subtype providing read access to the package file."""
return _PhysPkgReader.factory(self._pkg_file)
-class PackageWriter(object):
+class PackageWriter:
"""Writes a zip-format OPC package to `pkg_file`.
- `pkg_file` can be either a path to a zip file (a string) or a file-like object.
- `pkg_rels` is the |_Relationships| object containing relationships for the package.
- `parts` is a sequence of |Part| subtype instance to be written to the package.
+ `pkg_file` can be either a path to a zip file (a string) or a file-like object. `pkg_rels` is
+ the |_Relationships| object containing relationships for the package. `parts` is a sequence of
+ |Part| subtype instance to be written to the package.
- Its single API classmethod is :meth:`write`. This class is not intended to be
- instantiated.
+ Its single API classmethod is :meth:`write`. This class is not intended to be instantiated.
"""
- def __init__(self, pkg_file, pkg_rels, parts):
+ def __init__(self, pkg_file: str | IO[bytes], pkg_rels: _Relationships, parts: Sequence[Part]):
self._pkg_file = pkg_file
self._pkg_rels = pkg_rels
self._parts = parts
@classmethod
- def write(cls, pkg_file, pkg_rels, parts):
+ def write(
+ cls, pkg_file: str | IO[bytes], pkg_rels: _Relationships, parts: Sequence[Part]
+ ) -> None:
"""Write a physical package (.pptx file) to `pkg_file`.
- The serialized package contains `pkg_rels` and `parts`, a content-types stream
- based on the content type of each part, and a .rels file for each part that has
- relationships.
+ The serialized package contains `pkg_rels` and `parts`, a content-types stream based on
+ the content type of each part, and a .rels file for each part that has relationships.
"""
cls(pkg_file, pkg_rels, parts)._write()
- def _write(self):
+ def _write(self) -> None:
"""Write physical package (.pptx file)."""
with _PhysPkgWriter.factory(self._pkg_file) as phys_writer:
self._write_content_types_stream(phys_writer)
self._write_pkg_rels(phys_writer)
self._write_parts(phys_writer)
- def _write_content_types_stream(self, phys_writer):
+ def _write_content_types_stream(self, phys_writer: _PhysPkgWriter) -> None:
"""Write `[Content_Types].xml` part to the physical package.
- This part must contain an appropriate content type lookup target for each part
- in the package.
+ This part must contain an appropriate content type lookup target for each part in the
+ package.
"""
phys_writer.write(
CONTENT_TYPES_URI,
serialize_part_xml(_ContentTypesItem.xml_for(self._parts)),
)
- def _write_parts(self, phys_writer):
+ def _write_parts(self, phys_writer: _PhysPkgWriter) -> None:
"""Write blob of each part in `parts` to the package.
A rels item for each part is also written when the part has relationships.
"""
for part in self._parts:
phys_writer.write(part.partname, part.blob)
- if part._rels:
+ if part._rels: # pyright: ignore[reportPrivateUsage]
phys_writer.write(part.partname.rels_uri, part.rels.xml)
- def _write_pkg_rels(self, phys_writer):
- """Write the XML rels item for *pkg_rels* ('/_rels/.rels') to the package."""
+ def _write_pkg_rels(self, phys_writer: _PhysPkgWriter) -> None:
+ """Write the XML rels item for `pkg_rels` ('/_rels/.rels') to the package."""
phys_writer.write(PACKAGE_URI.rels_uri, self._pkg_rels.xml)
-class _PhysPkgReader(Container):
+class _PhysPkgReader(Container[PackURI]):
"""Base class for physical package reader objects."""
- def __contains__(self, item):
+ def __contains__(self, item: object) -> bool:
"""Must be implemented by each subclass."""
raise NotImplementedError( # pragma: no cover
"`%s` must implement `.__contains__()`" % type(self).__name__
)
+ def __getitem__(self, pack_uri: PackURI) -> bytes:
+ """Blob for part corresponding to `pack_uri`."""
+ raise NotImplementedError( # pragma: no cover
+ f"`{type(self).__name__}` must implement `.__contains__()`"
+ )
+
@classmethod
- def factory(cls, pkg_file):
+ def factory(cls, pkg_file: str | IO[bytes]) -> _PhysPkgReader:
"""Return |_PhysPkgReader| subtype instance appropriage for `pkg_file`."""
# --- for pkg_file other than str, assume it's a stream and pass it to Zip
# --- reader to sort out
- if not is_string(pkg_file):
+ if not isinstance(pkg_file, str):
return _ZipPkgReader(pkg_file)
# --- otherwise we treat `pkg_file` as a path ---
@@ -141,14 +151,16 @@ class _DirPkgReader(_PhysPkgReader):
`path` is the path to a directory containing an expanded package.
"""
- def __init__(self, path):
+ def __init__(self, path: str):
self._path = os.path.abspath(path)
- def __contains__(self, pack_uri):
+ def __contains__(self, pack_uri: object) -> bool:
"""Return True when part identified by `pack_uri` is present in zip archive."""
+ if not isinstance(pack_uri, PackURI):
+ return False
return os.path.exists(posixpath.join(self._path, pack_uri.membername))
- def __getitem__(self, pack_uri):
+ def __getitem__(self, pack_uri: PackURI) -> bytes:
"""Return bytes of file corresponding to `pack_uri` in package directory."""
path = os.path.join(self._path, pack_uri.membername)
try:
@@ -161,14 +173,14 @@ def __getitem__(self, pack_uri):
class _ZipPkgReader(_PhysPkgReader):
"""Implements |PhysPkgReader| interface for a zip-file OPC package."""
- def __init__(self, pkg_file):
+ def __init__(self, pkg_file: str | IO[bytes]):
self._pkg_file = pkg_file
- def __contains__(self, pack_uri):
+ def __contains__(self, pack_uri: object) -> bool:
"""Return True when part identified by `pack_uri` is present in zip archive."""
return pack_uri in self._blobs
- def __getitem__(self, pack_uri):
+ def __getitem__(self, pack_uri: PackURI) -> bytes:
"""Return bytes for part corresponding to `pack_uri`.
Raises |KeyError| if no matching member is present in zip archive.
@@ -178,76 +190,80 @@ def __getitem__(self, pack_uri):
return self._blobs[pack_uri]
@lazyproperty
- def _blobs(self):
+ def _blobs(self) -> dict[PackURI, bytes]:
"""dict mapping partname to package part binaries."""
with zipfile.ZipFile(self._pkg_file, "r") as z:
return {PackURI("/%s" % name): z.read(name) for name in z.namelist()}
-class _PhysPkgWriter(object):
+class _PhysPkgWriter:
"""Base class for physical package writer objects."""
@classmethod
- def factory(cls, pkg_file):
+ def factory(cls, pkg_file: str | IO[bytes]) -> _ZipPkgWriter:
"""Return |_PhysPkgWriter| subtype instance appropriage for `pkg_file`.
- Currently the only subtype is `_ZipPkgWriter`, but a `_DirPkgWriter` could be
- implemented or even a `_StreamPkgWriter`.
+ Currently the only subtype is `_ZipPkgWriter`, but a `_DirPkgWriter` could be implemented
+ or even a `_StreamPkgWriter`.
"""
return _ZipPkgWriter(pkg_file)
+ def write(self, pack_uri: PackURI, blob: bytes) -> None:
+ """Write `blob` to package with membername corresponding to `pack_uri`."""
+ raise NotImplementedError( # pragma: no cover
+ f"`{type(self).__name__}` must implement `.write()`"
+ )
+
class _ZipPkgWriter(_PhysPkgWriter):
"""Implements |PhysPkgWriter| interface for a zip-file (.pptx file) OPC package."""
- def __init__(self, pkg_file):
+ def __init__(self, pkg_file: str | IO[bytes]):
self._pkg_file = pkg_file
- def __enter__(self):
+ def __enter__(self) -> _ZipPkgWriter:
"""Enable use as a context-manager. Opening zip for writing happens here."""
return self
- def __exit__(self, exc_type, exc_value, exc_traceback):
+ def __exit__(self, *exc: list[Any]) -> None:
"""Close the zip archive on exit from context.
- Closing flushes any pending physical writes and releasing any resources it's
- using.
+ Closing flushes any pending physical writes and releasing any resources it's using.
"""
self._zipf.close()
- def write(self, pack_uri, blob):
+ def write(self, pack_uri: PackURI, blob: bytes) -> None:
"""Write `blob` to zip package with membername corresponding to `pack_uri`."""
self._zipf.writestr(pack_uri.membername, blob)
@lazyproperty
- def _zipf(self):
+ def _zipf(self) -> zipfile.ZipFile:
"""`ZipFile` instance open for writing."""
return zipfile.ZipFile(self._pkg_file, "w", compression=zipfile.ZIP_DEFLATED)
-class _ContentTypesItem(object):
+class _ContentTypesItem:
"""Composes content-types "part" ([Content_Types].xml) for a collection of parts."""
- def __init__(self, parts):
+ def __init__(self, parts: Sequence[Part]):
self._parts = parts
@classmethod
- def xml_for(cls, parts):
+ def xml_for(cls, parts: Sequence[Part]) -> CT_Types:
"""Return content-types XML mapping each part in `parts` to a content-type.
- The resulting XML is suitable for storage as `[Content_Types].xml` in an OPC
- package.
+ The resulting XML is suitable for storage as `[Content_Types].xml` in an OPC package.
"""
return cls(parts)._xml
@lazyproperty
- def _xml(self):
+ def _xml(self) -> CT_Types:
"""lxml.etree._Element containing the content-types item.
- This XML object is suitable for serialization to the `[Content_Types].xml` item
- for an OPC package. Although the sequence of elements is not strictly
- significant, as an aid to testing and readability Default elements are sorted by
- extension and Override elements are sorted by partname.
+ This XML object is suitable for serialization to the `[Content_Types].xml` item for an OPC
+ package. Although the sequence of elements is not strictly significant, as an aid to
+ testing and readability Default elements are sorted by extension and Override elements are
+ sorted by partname.
"""
defaults, overrides = self._defaults_and_overrides
_types_elm = CT_Types.new()
@@ -260,13 +276,13 @@ def _xml(self):
return _types_elm
@lazyproperty
- def _defaults_and_overrides(self):
+ def _defaults_and_overrides(self) -> tuple[dict[str, str], dict[PackURI, str]]:
"""pair of dict (defaults, overrides) accounting for all parts.
`defaults` is {ext: content_type} and overrides is {partname: content_type}.
"""
defaults = CaseInsensitiveDict(rels=CT.OPC_RELATIONSHIPS, xml=CT.XML)
- overrides = dict()
+ overrides: dict[PackURI, str] = {}
for part in self._parts:
partname, content_type = part.partname, part.content_type
diff --git a/src/pptx/opc/shared.py b/src/pptx/opc/shared.py
index 95e379984..cc7fce8c1 100644
--- a/src/pptx/opc/shared.py
+++ b/src/pptx/opc/shared.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Objects shared by modules in the pptx.opc sub-package."""
+from __future__ import annotations
+
class CaseInsensitiveDict(dict):
"""Mapping type like dict except it matches key without respect to case.
diff --git a/src/pptx/opc/spec.py b/src/pptx/opc/spec.py
index 5b63f425c..a83caf8bd 100644
--- a/src/pptx/opc/spec.py
+++ b/src/pptx/opc/spec.py
@@ -1,11 +1,6 @@
-# encoding: utf-8
-
-"""
-Provides mappings that embody aspects of the Open XML spec ISO/IEC 29500.
-"""
-
-from .constants import CONTENT_TYPE as CT
+"""Provides mappings that embody aspects of the Open XML spec ISO/IEC 29500."""
+from pptx.opc.constants import CONTENT_TYPE as CT
default_content_types = (
("bin", CT.PML_PRINTER_SETTINGS),
diff --git a/src/pptx/oxml/__init__.py b/src/pptx/oxml/__init__.py
index 099960d72..21afaa921 100644
--- a/src/pptx/oxml/__init__.py
+++ b/src/pptx/oxml/__init__.py
@@ -1,64 +1,58 @@
-# encoding: utf-8
-
"""Initializes lxml parser, particularly the custom element classes.
Also makes available a handful of functions that wrap its typical uses.
"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import os
+from typing import TYPE_CHECKING, Type
from lxml import etree
-from .ns import NamespacePrefixedTag
+from pptx.oxml.ns import NamespacePrefixedTag
+
+if TYPE_CHECKING:
+ from pptx.oxml.xmlchemy import BaseOxmlElement
-# configure etree XML parser -------------------------------
+# -- configure etree XML parser ----------------------------
element_class_lookup = etree.ElementNamespaceClassLookup()
oxml_parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False)
oxml_parser.set_element_class_lookup(element_class_lookup)
-def parse_from_template(template_name):
- """
- Return an element loaded from the XML in the template file identified by
- *template_name*.
- """
+def parse_from_template(template_file_name: str):
+ """Return an element loaded from the XML in the template file identified by `template_name`."""
thisdir = os.path.split(__file__)[0]
- filename = os.path.join(thisdir, "..", "templates", "%s.xml" % template_name)
+ filename = os.path.join(thisdir, "..", "templates", "%s.xml" % template_file_name)
with open(filename, "rb") as f:
xml = f.read()
return parse_xml(xml)
-def parse_xml(xml):
- """
- Return root lxml element obtained by parsing XML character string in
- *xml*, which can be either a Python 2.x string or unicode.
- """
- root_element = etree.fromstring(xml, oxml_parser)
- return root_element
+def parse_xml(xml: str | bytes):
+ """Return root lxml element obtained by parsing XML character string in `xml`."""
+ return etree.fromstring(xml, oxml_parser)
-def register_element_cls(nsptagname, cls):
- """
- Register *cls* to be constructed when the oxml parser encounters an
- element having name *nsptag_name*. *nsptag_name* is a string of the form
- ``nspfx:tagroot``, e.g. ``'w:document'``.
+def register_element_cls(nsptagname: str, cls: Type[BaseOxmlElement]):
+ """Register `cls` to be constructed when oxml parser encounters element having `nsptag_name`.
+
+ `nsptag_name` is a string of the form `nspfx:tagroot`, e.g. `"w:document"`.
"""
nsptag = NamespacePrefixedTag(nsptagname)
namespace = element_class_lookup.get_namespace(nsptag.nsuri)
namespace[nsptag.local_part] = cls
-from .action import CT_Hyperlink # noqa: E402
+from pptx.oxml.action import CT_Hyperlink # noqa: E402
register_element_cls("a:hlinkClick", CT_Hyperlink)
register_element_cls("a:hlinkHover", CT_Hyperlink)
-from .chart.axis import ( # noqa: E402
+from pptx.oxml.chart.axis import ( # noqa: E402
CT_AxisUnit,
CT_CatAx,
CT_ChartLines,
@@ -87,7 +81,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls("c:valAx", CT_ValAx)
-from .chart.chart import ( # noqa: E402
+from pptx.oxml.chart.chart import ( # noqa: E402
CT_Chart,
CT_ChartSpace,
CT_ExternalData,
@@ -102,27 +96,27 @@ def register_element_cls(nsptagname, cls):
register_element_cls("c:style", CT_Style)
-from .chart.datalabel import CT_DLbl, CT_DLblPos, CT_DLbls # noqa: E402
+from pptx.oxml.chart.datalabel import CT_DLbl, CT_DLblPos, CT_DLbls # noqa: E402
register_element_cls("c:dLbl", CT_DLbl)
register_element_cls("c:dLblPos", CT_DLblPos)
register_element_cls("c:dLbls", CT_DLbls)
-from .chart.legend import CT_Legend, CT_LegendPos # noqa: E402
+from pptx.oxml.chart.legend import CT_Legend, CT_LegendPos # noqa: E402
register_element_cls("c:legend", CT_Legend)
register_element_cls("c:legendPos", CT_LegendPos)
-from .chart.marker import CT_Marker, CT_MarkerSize, CT_MarkerStyle # noqa: E402
+from pptx.oxml.chart.marker import CT_Marker, CT_MarkerSize, CT_MarkerStyle # noqa: E402
register_element_cls("c:marker", CT_Marker)
register_element_cls("c:size", CT_MarkerSize)
register_element_cls("c:symbol", CT_MarkerStyle)
-from .chart.plot import ( # noqa: E402
+from pptx.oxml.chart.plot import ( # noqa: E402
CT_Area3DChart,
CT_AreaChart,
CT_BarChart,
@@ -155,7 +149,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls("c:scatterChart", CT_ScatterChart)
-from .chart.series import ( # noqa: E402
+from pptx.oxml.chart.series import ( # noqa: E402
CT_AxDataSource,
CT_DPt,
CT_Lvl,
@@ -175,7 +169,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls("c:yVal", CT_NumDataSource)
-from .chart.shared import ( # noqa: E402
+from pptx.oxml.chart.shared import ( # noqa: E402
CT_Boolean,
CT_Boolean_Explicit,
CT_Double,
@@ -218,12 +212,12 @@ def register_element_cls(nsptagname, cls):
register_element_cls("c:xMode", CT_LayoutMode)
-from .coreprops import CT_CoreProperties # noqa: E402
+from pptx.oxml.coreprops import CT_CoreProperties # noqa: E402
register_element_cls("cp:coreProperties", CT_CoreProperties)
-from .dml.color import ( # noqa: E402
+from pptx.oxml.dml.color import ( # noqa: E402
CT_Color,
CT_HslColor,
CT_Percentage,
@@ -246,7 +240,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls("a:sysClr", CT_SystemColor)
-from .dml.fill import ( # noqa: E402
+from pptx.oxml.dml.fill import ( # noqa: E402
CT_Blip,
CT_BlipFillProperties,
CT_GradientFillProperties,
@@ -273,12 +267,12 @@ def register_element_cls(nsptagname, cls):
register_element_cls("a:srcRect", CT_RelativeRect)
-from .dml.line import CT_PresetLineDashProperties # noqa: E402
+from pptx.oxml.dml.line import CT_PresetLineDashProperties # noqa: E402
register_element_cls("a:prstDash", CT_PresetLineDashProperties)
-from .presentation import ( # noqa: E402
+from pptx.oxml.presentation import ( # noqa: E402
CT_Presentation,
CT_SlideId,
CT_SlideIdList,
@@ -295,7 +289,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls("p:sldSz", CT_SlideSize)
-from .shapes.autoshape import ( # noqa: E402
+from pptx.oxml.shapes.autoshape import ( # noqa: E402
CT_AdjPoint2D,
CT_CustomGeometry2D,
CT_GeomGuide,
@@ -326,7 +320,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls("p:sp", CT_Shape)
-from .shapes.connector import ( # noqa: E402
+from pptx.oxml.shapes.connector import ( # noqa: E402
CT_Connection,
CT_Connector,
CT_ConnectorNonVisual,
@@ -340,7 +334,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls("p:nvCxnSpPr", CT_ConnectorNonVisual)
-from .shapes.graphfrm import ( # noqa: E402
+from pptx.oxml.shapes.graphfrm import ( # noqa: E402
CT_GraphicalObject,
CT_GraphicalObjectData,
CT_GraphicalObjectFrame,
@@ -355,7 +349,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls("p:oleObj", CT_OleObject)
-from .shapes.groupshape import ( # noqa: E402
+from pptx.oxml.shapes.groupshape import ( # noqa: E402
CT_GroupShape,
CT_GroupShapeNonVisual,
CT_GroupShapeProperties,
@@ -367,14 +361,14 @@ def register_element_cls(nsptagname, cls):
register_element_cls("p:spTree", CT_GroupShape)
-from .shapes.picture import CT_Picture, CT_PictureNonVisual # noqa: E402
+from pptx.oxml.shapes.picture import CT_Picture, CT_PictureNonVisual # noqa: E402
register_element_cls("p:blipFill", CT_BlipFillProperties)
register_element_cls("p:nvPicPr", CT_PictureNonVisual)
register_element_cls("p:pic", CT_Picture)
-from .shapes.shared import ( # noqa: E402
+from pptx.oxml.shapes.shared import ( # noqa: E402
CT_ApplicationNonVisualDrawingProps,
CT_LineProperties,
CT_NonVisualDrawingProps,
@@ -399,7 +393,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls("p:xfrm", CT_Transform2D)
-from .slide import ( # noqa: E402
+from pptx.oxml.slide import ( # noqa: E402
CT_Background,
CT_BackgroundProperties,
CT_CommonSlideData,
@@ -430,7 +424,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls("p:video", CT_TLMediaNodeVideo)
-from .table import ( # noqa: E402
+from pptx.oxml.table import ( # noqa: E402
CT_Table,
CT_TableCell,
CT_TableCellProperties,
@@ -449,7 +443,7 @@ def register_element_cls(nsptagname, cls):
register_element_cls("a:tr", CT_TableRow)
-from .text import ( # noqa: E402
+from pptx.oxml.text import ( # noqa: E402
CT_RegularTextRun,
CT_TextBody,
CT_TextBodyProperties,
@@ -487,6 +481,6 @@ def register_element_cls(nsptagname, cls):
register_element_cls("p:txBody", CT_TextBody)
-from .theme import CT_OfficeStyleSheet # noqa: E402
+from pptx.oxml.theme import CT_OfficeStyleSheet # noqa: E402
register_element_cls("a:theme", CT_OfficeStyleSheet)
diff --git a/src/pptx/oxml/action.py b/src/pptx/oxml/action.py
index baaeb923d..9b31a9e16 100644
--- a/src/pptx/oxml/action.py
+++ b/src/pptx/oxml/action.py
@@ -1,31 +1,26 @@
-# encoding: utf-8
+"""lxml custom element classes for text-related XML elements."""
-"""
-lxml custom element classes for text-related XML elements.
-"""
+from __future__ import annotations
-from __future__ import absolute_import
-
-from .simpletypes import XsdString
-from .xmlchemy import BaseOxmlElement, OptionalAttribute
+from pptx.oxml.simpletypes import XsdString
+from pptx.oxml.xmlchemy import BaseOxmlElement, OptionalAttribute
class CT_Hyperlink(BaseOxmlElement):
- """
- Custom element class for elements.
- """
+ """Custom element class for elements."""
- rId = OptionalAttribute("r:id", XsdString)
- action = OptionalAttribute("action", XsdString)
+ rId: str = OptionalAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
+ action: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "action", XsdString
+ )
@property
- def action_fields(self):
- """
- A dictionary containing any key-value pairs present in the query
- portion of the `ppaction://` URL in the action attribute. For example
- `{'id':'0', 'return':'true'}` in
- 'ppaction://customshow?id=0&return=true'. Returns an empty dictionary
- if the URL contains no query string or if no action attribute is
+ def action_fields(self) -> dict[str, str]:
+ """Query portion of the `ppaction://` URL as dict.
+
+ For example `{'id':'0', 'return':'true'}` in 'ppaction://customshow?id=0&return=true'.
+
+ Returns an empty dict if the URL contains no query string or if no action attribute is
present.
"""
url = self.action
@@ -41,12 +36,11 @@ def action_fields(self):
return dict([pair.split("=") for pair in key_value_pairs])
@property
- def action_verb(self):
- """
- The host portion of the `ppaction://` URL contained in the action
- attribute. For example 'customshow' in
- 'ppaction://customshow?id=0&return=true'. Returns |None| if no action
- attribute is present.
+ def action_verb(self) -> str | None:
+ """The host portion of the `ppaction://` URL contained in the action attribute.
+
+ For example 'customshow' in 'ppaction://customshow?id=0&return=true'. Returns |None| if no
+ action attribute is present.
"""
url = self.action
diff --git a/src/pptx/oxml/chart/axis.py b/src/pptx/oxml/chart/axis.py
index c59d2440a..7129810c9 100644
--- a/src/pptx/oxml/chart/axis.py
+++ b/src/pptx/oxml/chart/axis.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
-
"""Axis-related oxml objects."""
-from __future__ import unicode_literals
+from __future__ import annotations
from pptx.enum.chart import XL_AXIS_CROSSES, XL_TICK_LABEL_POSITION, XL_TICK_MARK
from pptx.oxml.chart.shared import CT_Title
diff --git a/src/pptx/oxml/chart/chart.py b/src/pptx/oxml/chart/chart.py
index 65a0191e7..f4cd0dc7c 100644
--- a/src/pptx/oxml/chart/chart.py
+++ b/src/pptx/oxml/chart/chart.py
@@ -1,8 +1,8 @@
-# encoding: utf-8
-
"""Custom element classes for top-level chart-related XML elements."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from typing import cast
from pptx.oxml import parse_xml
from pptx.oxml.chart.shared import CT_Title
@@ -40,9 +40,7 @@ class CT_Chart(BaseOxmlElement):
autoTitleDeleted = ZeroOrOne("c:autoTitleDeleted", successors=_tag_seq[2:])
plotArea = OneAndOnlyOne("c:plotArea")
legend = ZeroOrOne("c:legend", successors=_tag_seq[9:])
- rId = RequiredAttribute("r:id", XsdString)
-
- _chart_tmpl = '' % (nsdecls("c"), nsdecls("r"))
+ rId: str = RequiredAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
@property
def has_legend(self):
@@ -69,13 +67,9 @@ def has_legend(self, bool_value):
self._add_legend()
@staticmethod
- def new_chart(rId):
- """
- Return a new ```` element
- """
- xml = CT_Chart._chart_tmpl % (rId)
- chart = parse_xml(xml)
- return chart
+ def new_chart(rId: str) -> CT_Chart:
+ """Return a new `c:chart` element."""
+ return cast(CT_Chart, parse_xml(f''))
def _new_title(self):
return CT_Title.new_title()
diff --git a/src/pptx/oxml/chart/datalabel.py b/src/pptx/oxml/chart/datalabel.py
index 091693919..b6aac2fd5 100644
--- a/src/pptx/oxml/chart/datalabel.py
+++ b/src/pptx/oxml/chart/datalabel.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
-
"""Chart data-label related oxml objects."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from pptx.enum.chart import XL_DATA_LABEL_POSITION
from pptx.oxml import parse_xml
diff --git a/src/pptx/oxml/chart/legend.py b/src/pptx/oxml/chart/legend.py
index 7a2eadb8e..196ca15de 100644
--- a/src/pptx/oxml/chart/legend.py
+++ b/src/pptx/oxml/chart/legend.py
@@ -1,14 +1,10 @@
-# encoding: utf-8
+"""lxml custom element classes for legend-related XML elements."""
-"""
-lxml custom element classes for legend-related XML elements.
-"""
+from __future__ import annotations
-from __future__ import absolute_import, print_function, unicode_literals
-
-from ...enum.chart import XL_LEGEND_POSITION
-from ..text import CT_TextBody
-from ..xmlchemy import BaseOxmlElement, OptionalAttribute, ZeroOrOne
+from pptx.enum.chart import XL_LEGEND_POSITION
+from pptx.oxml.text import CT_TextBody
+from pptx.oxml.xmlchemy import BaseOxmlElement, OptionalAttribute, ZeroOrOne
class CT_Legend(BaseOxmlElement):
diff --git a/src/pptx/oxml/chart/marker.py b/src/pptx/oxml/chart/marker.py
index e849e3be2..34afd13d5 100644
--- a/src/pptx/oxml/chart/marker.py
+++ b/src/pptx/oxml/chart/marker.py
@@ -1,14 +1,10 @@
-# encoding: utf-8
+"""Series-related oxml objects."""
-"""
-Series-related oxml objects.
-"""
+from __future__ import annotations
-from __future__ import absolute_import, division, print_function, unicode_literals
-
-from ...enum.chart import XL_MARKER_STYLE
-from ..simpletypes import ST_MarkerSize
-from ..xmlchemy import BaseOxmlElement, RequiredAttribute, ZeroOrOne
+from pptx.enum.chart import XL_MARKER_STYLE
+from pptx.oxml.simpletypes import ST_MarkerSize
+from pptx.oxml.xmlchemy import BaseOxmlElement, RequiredAttribute, ZeroOrOne
class CT_Marker(BaseOxmlElement):
diff --git a/src/pptx/oxml/chart/plot.py b/src/pptx/oxml/chart/plot.py
index f917913df..9c695a43a 100644
--- a/src/pptx/oxml/chart/plot.py
+++ b/src/pptx/oxml/chart/plot.py
@@ -1,25 +1,21 @@
-# encoding: utf-8
+"""Plot-related oxml objects."""
-"""
-Plot-related oxml objects.
-"""
+from __future__ import annotations
-from __future__ import absolute_import, print_function, unicode_literals
-
-from .datalabel import CT_DLbls
-from ..simpletypes import (
+from pptx.oxml.chart.datalabel import CT_DLbls
+from pptx.oxml.simpletypes import (
ST_BarDir,
ST_BubbleScale,
ST_GapAmount,
ST_Grouping,
ST_Overlap,
)
-from ..xmlchemy import (
+from pptx.oxml.xmlchemy import (
BaseOxmlElement,
OneAndOnlyOne,
OptionalAttribute,
- ZeroOrOne,
ZeroOrMore,
+ ZeroOrOne,
)
diff --git a/src/pptx/oxml/chart/series.py b/src/pptx/oxml/chart/series.py
index 2974a2269..9264d552d 100644
--- a/src/pptx/oxml/chart/series.py
+++ b/src/pptx/oxml/chart/series.py
@@ -1,14 +1,10 @@
-# encoding: utf-8
+"""Series-related oxml objects."""
-"""
-Series-related oxml objects.
-"""
+from __future__ import annotations
-from __future__ import absolute_import, print_function, unicode_literals
-
-from .datalabel import CT_DLbls
-from ..simpletypes import XsdUnsignedInt
-from ..xmlchemy import (
+from pptx.oxml.chart.datalabel import CT_DLbls
+from pptx.oxml.simpletypes import XsdUnsignedInt
+from pptx.oxml.xmlchemy import (
BaseOxmlElement,
OneAndOnlyOne,
OxmlElement,
diff --git a/src/pptx/oxml/chart/shared.py b/src/pptx/oxml/chart/shared.py
index ddea5132c..5515aa4be 100644
--- a/src/pptx/oxml/chart/shared.py
+++ b/src/pptx/oxml/chart/shared.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
-
"""Shared oxml objects for charts."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from pptx.oxml import parse_xml
from pptx.oxml.ns import nsdecls
@@ -186,10 +184,7 @@ def tx_rich(self):
def new_title():
"""Return "loose" `c:title` element containing default children."""
return parse_xml(
- ""
- " "
- ' '
- "" % nsdecls("c")
+ "" " " ' ' "" % nsdecls("c")
)
diff --git a/src/pptx/oxml/coreprops.py b/src/pptx/oxml/coreprops.py
index 2993e88bc..de6b26b24 100644
--- a/src/pptx/oxml/coreprops.py
+++ b/src/pptx/oxml/coreprops.py
@@ -1,30 +1,29 @@
-# encoding: utf-8
+"""lxml custom element classes for core properties-related XML elements."""
-"""
-lxml custom element classes for core properties-related XML elements.
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+import datetime as dt
import re
+from typing import Callable, cast
-from datetime import datetime, timedelta
+from lxml.etree import _Element # pyright: ignore[reportPrivateUsage]
-from pptx.compat import to_unicode
-from . import parse_xml
-from .ns import nsdecls, qn
-from .xmlchemy import BaseOxmlElement, ZeroOrOne
+from pptx.oxml import parse_xml
+from pptx.oxml.ns import nsdecls, qn
+from pptx.oxml.xmlchemy import BaseOxmlElement, ZeroOrOne
class CT_CoreProperties(BaseOxmlElement):
- """
- ```` element, the root element of the Core Properties
- part stored as ``/docProps/core.xml``. Implements many of the Dublin Core
- document metadata elements. String elements resolve to an empty string
- ('') if the element is not present in the XML. String elements are
- limited in length to 255 unicode characters.
+ """`cp:coreProperties` element.
+
+ The root element of the Core Properties part stored as `/docProps/core.xml`. Implements many
+ of the Dublin Core document metadata elements. String elements resolve to an empty string ('')
+ if the element is not present in the XML. String elements are limited in length to 255 unicode
+ characters.
"""
+ get_or_add_revision: Callable[[], _Element]
+
category = ZeroOrOne("cp:category", successors=())
contentStatus = ZeroOrOne("cp:contentStatus", successors=())
created = ZeroOrOne("dcterms:created", successors=())
@@ -36,7 +35,9 @@ class CT_CoreProperties(BaseOxmlElement):
lastModifiedBy = ZeroOrOne("cp:lastModifiedBy", successors=())
lastPrinted = ZeroOrOne("cp:lastPrinted", successors=())
modified = ZeroOrOne("dcterms:modified", successors=())
- revision = ZeroOrOne("cp:revision", successors=())
+ revision: _Element | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "cp:revision", successors=()
+ )
subject = ZeroOrOne("dc:subject", successors=())
title = ZeroOrOne("dc:title", successors=())
version = ZeroOrOne("cp:version", successors=())
@@ -44,42 +45,40 @@ class CT_CoreProperties(BaseOxmlElement):
_coreProperties_tmpl = "\n" % nsdecls("cp", "dc", "dcterms")
@staticmethod
- def new_coreProperties():
- """Return a new ```` element"""
- xml = CT_CoreProperties._coreProperties_tmpl
- coreProperties = parse_xml(xml)
- return coreProperties
+ def new_coreProperties() -> CT_CoreProperties:
+ """Return a new `cp:coreProperties` element"""
+ return cast(CT_CoreProperties, parse_xml(CT_CoreProperties._coreProperties_tmpl))
@property
- def author_text(self):
+ def author_text(self) -> str:
return self._text_of_element("creator")
@author_text.setter
- def author_text(self, value):
+ def author_text(self, value: str):
self._set_element_text("creator", value)
@property
- def category_text(self):
+ def category_text(self) -> str:
return self._text_of_element("category")
@category_text.setter
- def category_text(self, value):
+ def category_text(self, value: str):
self._set_element_text("category", value)
@property
- def comments_text(self):
+ def comments_text(self) -> str:
return self._text_of_element("description")
@comments_text.setter
- def comments_text(self, value):
+ def comments_text(self, value: str):
self._set_element_text("description", value)
@property
- def contentStatus_text(self):
+ def contentStatus_text(self) -> str:
return self._text_of_element("contentStatus")
@contentStatus_text.setter
- def contentStatus_text(self, value):
+ def contentStatus_text(self, value: str):
self._set_element_text("contentStatus", value)
@property
@@ -87,39 +86,39 @@ def created_datetime(self):
return self._datetime_of_element("created")
@created_datetime.setter
- def created_datetime(self, value):
+ def created_datetime(self, value: dt.datetime):
self._set_element_datetime("created", value)
@property
- def identifier_text(self):
+ def identifier_text(self) -> str:
return self._text_of_element("identifier")
@identifier_text.setter
- def identifier_text(self, value):
+ def identifier_text(self, value: str):
self._set_element_text("identifier", value)
@property
- def keywords_text(self):
+ def keywords_text(self) -> str:
return self._text_of_element("keywords")
@keywords_text.setter
- def keywords_text(self, value):
+ def keywords_text(self, value: str):
self._set_element_text("keywords", value)
@property
- def language_text(self):
+ def language_text(self) -> str:
return self._text_of_element("language")
@language_text.setter
- def language_text(self, value):
+ def language_text(self, value: str):
self._set_element_text("language", value)
@property
- def lastModifiedBy_text(self):
+ def lastModifiedBy_text(self) -> str:
return self._text_of_element("lastModifiedBy")
@lastModifiedBy_text.setter
- def lastModifiedBy_text(self, value):
+ def lastModifiedBy_text(self, value: str):
self._set_element_text("lastModifiedBy", value)
@property
@@ -127,7 +126,7 @@ def lastPrinted_datetime(self):
return self._datetime_of_element("lastPrinted")
@lastPrinted_datetime.setter
- def lastPrinted_datetime(self, value):
+ def lastPrinted_datetime(self, value: dt.datetime):
self._set_element_datetime("lastPrinted", value)
@property
@@ -135,104 +134,101 @@ def modified_datetime(self):
return self._datetime_of_element("modified")
@modified_datetime.setter
- def modified_datetime(self, value):
+ def modified_datetime(self, value: dt.datetime):
self._set_element_datetime("modified", value)
@property
- def revision_number(self):
- """
- Integer value of revision property.
- """
+ def revision_number(self) -> int:
+ """Integer value of revision property."""
revision = self.revision
if revision is None:
return 0
revision_str = revision.text
+ if revision_str is None:
+ return 0
try:
revision = int(revision_str)
except ValueError:
- # non-integer revision strings also resolve to 0
- revision = 0
- # as do negative integers
+ # -- non-integer revision strings also resolve to 0 --
+ return 0
+ # -- as do negative integers --
if revision < 0:
- revision = 0
+ return 0
return revision
@revision_number.setter
- def revision_number(self, value):
- """
- Set revision property to string value of integer *value*.
- """
- if not isinstance(value, int) or value < 1:
+ def revision_number(self, value: int):
+ """Set revision property to string value of integer `value`."""
+ if not isinstance(value, int) or value < 1: # pyright: ignore[reportUnnecessaryIsInstance]
tmpl = "revision property requires positive int, got '%s'"
raise ValueError(tmpl % value)
revision = self.get_or_add_revision()
revision.text = str(value)
@property
- def subject_text(self):
+ def subject_text(self) -> str:
return self._text_of_element("subject")
@subject_text.setter
- def subject_text(self, value):
+ def subject_text(self, value: str):
self._set_element_text("subject", value)
@property
- def title_text(self):
+ def title_text(self) -> str:
return self._text_of_element("title")
@title_text.setter
- def title_text(self, value):
+ def title_text(self, value: str):
self._set_element_text("title", value)
@property
- def version_text(self):
+ def version_text(self) -> str:
return self._text_of_element("version")
@version_text.setter
- def version_text(self, value):
+ def version_text(self, value: str):
self._set_element_text("version", value)
- def _datetime_of_element(self, property_name):
- element = getattr(self, property_name)
+ def _datetime_of_element(self, property_name: str) -> dt.datetime | None:
+ element = cast("_Element | None", getattr(self, property_name))
if element is None:
return None
datetime_str = element.text
+ if datetime_str is None:
+ return None
try:
return self._parse_W3CDTF_to_datetime(datetime_str)
except ValueError:
# invalid datetime strings are ignored
return None
- def _get_or_add(self, prop_name):
- """
- Return element returned by 'get_or_add_' method for *prop_name*.
- """
+ def _get_or_add(self, prop_name: str):
+ """Return element returned by 'get_or_add_' method for `prop_name`."""
get_or_add_method_name = "get_or_add_%s" % prop_name
get_or_add_method = getattr(self, get_or_add_method_name)
element = get_or_add_method()
return element
@classmethod
- def _offset_dt(cls, dt, offset_str):
- """
- Return a |datetime| instance that is offset from datetime *dt* by
- the timezone offset specified in *offset_str*, a string like
- ``'-07:00'``.
+ def _offset_dt(cls, datetime: dt.datetime, offset_str: str):
+ """Return |datetime| instance offset from `datetime` by offset specified in `offset_str`.
+
+ `offset_str` is a string like `'-07:00'`.
"""
match = cls._offset_pattern.match(offset_str)
if match is None:
- raise ValueError("'%s' is not a valid offset string" % offset_str)
+ raise ValueError(f"{repr(offset_str)} is not a valid offset string")
sign, hours_str, minutes_str = match.groups()
sign_factor = -1 if sign == "+" else 1
hours = int(hours_str) * sign_factor
minutes = int(minutes_str) * sign_factor
- td = timedelta(hours=hours, minutes=minutes)
- return dt + td
+ td = dt.timedelta(hours=hours, minutes=minutes)
+ return datetime + td
_offset_pattern = re.compile(r"([+-])(\d\d):(\d\d)")
@classmethod
- def _parse_W3CDTF_to_datetime(cls, w3cdtf_str):
+ def _parse_W3CDTF_to_datetime(cls, w3cdtf_str: str) -> dt.datetime:
# valid W3CDTF date cases:
# yyyy e.g. '2003'
# yyyy-mm e.g. '2003-12'
@@ -244,24 +240,22 @@ def _parse_W3CDTF_to_datetime(cls, w3cdtf_str):
# '-07:30', so we have to do it ourselves
parseable_part = w3cdtf_str[:19]
offset_str = w3cdtf_str[19:]
- dt = None
+ timestamp = None
for tmpl in templates:
try:
- dt = datetime.strptime(parseable_part, tmpl)
+ timestamp = dt.datetime.strptime(parseable_part, tmpl)
except ValueError:
continue
- if dt is None:
+ if timestamp is None:
tmpl = "could not parse W3CDTF datetime string '%s'"
raise ValueError(tmpl % w3cdtf_str)
if len(offset_str) == 6:
- return cls._offset_dt(dt, offset_str)
- return dt
+ return cls._offset_dt(timestamp, offset_str)
+ return timestamp
- def _set_element_datetime(self, prop_name, value):
- """
- Set date/time value of child element having *prop_name* to *value*.
- """
- if not isinstance(value, datetime):
+ def _set_element_datetime(self, prop_name: str, value: dt.datetime) -> None:
+ """Set date/time value of child element having `prop_name` to `value`."""
+ if not isinstance(value, dt.datetime): # pyright: ignore[reportUnnecessaryIsInstance]
tmpl = "property requires object, got %s"
raise ValueError(tmpl % type(value))
element = self._get_or_add(prop_name)
@@ -276,16 +270,16 @@ def _set_element_datetime(self, prop_name, value):
element.set(qn("xsi:type"), "dcterms:W3CDTF")
del self.attrib[qn("xsi:foo")]
- def _set_element_text(self, prop_name, value):
- """Set string value of *name* property to *value*."""
- value = to_unicode(value)
+ def _set_element_text(self, prop_name: str, value: str) -> None:
+ """Set string value of `name` property to `value`."""
+ value = str(value)
if len(value) > 255:
tmpl = "exceeded 255 char limit for property, got:\n\n'%s'"
raise ValueError(tmpl % value)
element = self._get_or_add(prop_name)
element.text = value
- def _text_of_element(self, property_name):
+ def _text_of_element(self, property_name: str) -> str:
element = getattr(self, property_name)
if element is None:
return ""
diff --git a/src/pptx/oxml/dml/color.py b/src/pptx/oxml/dml/color.py
index 4aa796d5b..dfce90aa0 100644
--- a/src/pptx/oxml/dml/color.py
+++ b/src/pptx/oxml/dml/color.py
@@ -1,14 +1,10 @@
-# encoding: utf-8
+"""lxml custom element classes for DrawingML-related XML elements."""
-"""
-lxml custom element classes for DrawingML-related XML elements.
-"""
+from __future__ import annotations
-from __future__ import absolute_import
-
-from ...enum.dml import MSO_THEME_COLOR
-from ..simpletypes import ST_HexColorRGB, ST_Percentage
-from ..xmlchemy import (
+from pptx.enum.dml import MSO_THEME_COLOR
+from pptx.oxml.simpletypes import ST_HexColorRGB, ST_Percentage
+from pptx.oxml.xmlchemy import (
BaseOxmlElement,
Choice,
RequiredAttribute,
diff --git a/src/pptx/oxml/dml/fill.py b/src/pptx/oxml/dml/fill.py
index a7b688a3e..2ff2255d7 100644
--- a/src/pptx/oxml/dml/fill.py
+++ b/src/pptx/oxml/dml/fill.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""lxml custom element classes for DrawingML-related XML elements."""
-"""
-lxml custom element classes for DrawingML-related XML elements.
-"""
-
-from __future__ import absolute_import
+from __future__ import annotations
from pptx.enum.dml import MSO_PATTERN_TYPE
from pptx.oxml import parse_xml
@@ -165,17 +161,13 @@ class CT_PatternFillProperties(BaseOxmlElement):
def _new_bgClr(self):
"""Override default to add minimum subtree."""
- xml = (
- "\n" ' \n' "\n"
- ) % nsdecls("a")
+ xml = ("\n" ' \n' "\n") % nsdecls("a")
bgClr = parse_xml(xml)
return bgClr
def _new_fgClr(self):
"""Override default to add minimum subtree."""
- xml = (
- "\n" ' \n' "\n"
- ) % nsdecls("a")
+ xml = ("\n" ' \n' "\n") % nsdecls("a")
fgClr = parse_xml(xml)
return fgClr
diff --git a/src/pptx/oxml/dml/line.py b/src/pptx/oxml/dml/line.py
index 02d4e59c2..720ca8e07 100644
--- a/src/pptx/oxml/dml/line.py
+++ b/src/pptx/oxml/dml/line.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
-
"""lxml custom element classes for DrawingML line-related XML elements."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from pptx.enum.dml import MSO_LINE_DASH_STYLE
from pptx.oxml.xmlchemy import BaseOxmlElement, OptionalAttribute
diff --git a/src/pptx/oxml/ns.py b/src/pptx/oxml/ns.py
index f83c1cd0b..d900c33bf 100644
--- a/src/pptx/oxml/ns.py
+++ b/src/pptx/oxml/ns.py
@@ -1,66 +1,57 @@
-# encoding: utf-8
+"""Namespace related objects."""
-"""
-Namespace related objects.
-"""
+from __future__ import annotations
-from __future__ import absolute_import
-
-#: Maps namespace prefix to namespace name for all known PowerPoint XML
-#: namespaces.
+# -- Maps namespace prefix to namespace name for all known PowerPoint XML namespaces --
_nsmap = {
- "a": ("http://schemas.openxmlformats.org/drawingml/2006/main"),
- "c": ("http://schemas.openxmlformats.org/drawingml/2006/chart"),
- "cp": (
- "http://schemas.openxmlformats.org/package/2006/metadata/core-pro" "perties"
- ),
- "ct": ("http://schemas.openxmlformats.org/package/2006/content-types"),
- "dc": ("http://purl.org/dc/elements/1.1/"),
- "dcmitype": ("http://purl.org/dc/dcmitype/"),
- "dcterms": ("http://purl.org/dc/terms/"),
- "ep": (
- "http://schemas.openxmlformats.org/officeDocument/2006/extended-p" "roperties"
- ),
- "i": (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationsh" "ips/image"
- ),
- "m": ("http://schemas.openxmlformats.org/officeDocument/2006/math"),
- "mo": ("http://schemas.microsoft.com/office/mac/office/2008/main"),
- "mv": ("urn:schemas-microsoft-com:mac:vml"),
- "o": ("urn:schemas-microsoft-com:office:office"),
- "p": ("http://schemas.openxmlformats.org/presentationml/2006/main"),
- "pd": ("http://schemas.openxmlformats.org/drawingml/2006/presentationDra" "wing"),
- "pic": ("http://schemas.openxmlformats.org/drawingml/2006/picture"),
- "pr": ("http://schemas.openxmlformats.org/package/2006/relationships"),
- "r": ("http://schemas.openxmlformats.org/officeDocument/2006/relationsh" "ips"),
- "sl": (
- "http://schemas.openxmlformats.org/officeDocument/2006/relationsh"
- "ips/slideLayout"
- ),
- "v": ("urn:schemas-microsoft-com:vml"),
- "ve": ("http://schemas.openxmlformats.org/markup-compatibility/2006"),
- "w": ("http://schemas.openxmlformats.org/wordprocessingml/2006/main"),
- "w10": ("urn:schemas-microsoft-com:office:word"),
- "wne": ("http://schemas.microsoft.com/office/word/2006/wordml"),
- "wp": ("http://schemas.openxmlformats.org/drawingml/2006/wordprocessingD" "rawing"),
- "xsi": ("http://www.w3.org/2001/XMLSchema-instance"),
+ "a": "http://schemas.openxmlformats.org/drawingml/2006/main",
+ "c": "http://schemas.openxmlformats.org/drawingml/2006/chart",
+ "cp": "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
+ "ct": "http://schemas.openxmlformats.org/package/2006/content-types",
+ "dc": "http://purl.org/dc/elements/1.1/",
+ "dcmitype": "http://purl.org/dc/dcmitype/",
+ "dcterms": "http://purl.org/dc/terms/",
+ "ep": "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties",
+ "i": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
+ "m": "http://schemas.openxmlformats.org/officeDocument/2006/math",
+ "mo": "http://schemas.microsoft.com/office/mac/office/2008/main",
+ "mv": "urn:schemas-microsoft-com:mac:vml",
+ "o": "urn:schemas-microsoft-com:office:office",
+ "p": "http://schemas.openxmlformats.org/presentationml/2006/main",
+ "pd": "http://schemas.openxmlformats.org/drawingml/2006/presentationDrawing",
+ "pic": "http://schemas.openxmlformats.org/drawingml/2006/picture",
+ "pr": "http://schemas.openxmlformats.org/package/2006/relationships",
+ "r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
+ "sl": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout",
+ "v": "urn:schemas-microsoft-com:vml",
+ "ve": "http://schemas.openxmlformats.org/markup-compatibility/2006",
+ "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
+ "w10": "urn:schemas-microsoft-com:office:word",
+ "wne": "http://schemas.microsoft.com/office/word/2006/wordml",
+ "wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
+ "xsi": "http://www.w3.org/2001/XMLSchema-instance",
}
+pfxmap = {value: key for key, value in _nsmap.items()}
+
class NamespacePrefixedTag(str):
- """
- Value object that knows the semantics of an XML tag having a namespace
- prefix.
- """
+ """Value object that knows the semantics of an XML tag having a namespace prefix."""
- def __new__(cls, nstag, *args):
+ def __new__(cls, nstag: str):
return super(NamespacePrefixedTag, cls).__new__(cls, nstag)
- def __init__(self, nstag):
+ def __init__(self, nstag: str):
self._pfx, self._local_part = nstag.split(":")
self._ns_uri = _nsmap[self._pfx]
+ @classmethod
+ def from_clark_name(cls, clark_name: str) -> NamespacePrefixedTag:
+ nsuri, local_name = clark_name[1:].split("}")
+ nstag = "%s:%s" % (pfxmap[nsuri], local_name)
+ return cls(nstag)
+
@property
def clark_name(self):
return "{%s}%s" % (self._ns_uri, self._local_part)
@@ -100,40 +91,39 @@ def nsuri(self):
return self._ns_uri
-def namespaces(*prefixes):
- """
- Return a dict containing the subset namespace prefix mappings specified by
- *prefixes*. Any number of namespace prefixes can be supplied, e.g.
- namespaces('a', 'r', 'p').
+def namespaces(*prefixes: str):
+ """Return a dict containing the subset namespace prefix mappings specified by *prefixes*.
+
+ Any number of namespace prefixes can be supplied, e.g. namespaces('a', 'r', 'p').
"""
- namespaces = {}
- for prefix in prefixes:
- namespaces[prefix] = _nsmap[prefix]
- return namespaces
+ return {pfx: _nsmap[pfx] for pfx in prefixes}
nsmap = namespaces # alias for more compact use with Element()
-def nsdecls(*prefixes):
+def nsdecls(*prefixes: str):
return " ".join(['xmlns:%s="%s"' % (pfx, _nsmap[pfx]) for pfx in prefixes])
-def nsuri(nspfx):
- """
- Return the namespace URI corresponding to *nspfx*. For example, it would
- return 'http://foo/bar' for an *nspfx* of 'f' if the 'f' prefix maps to
- 'http://foo/bar' in _nsmap.
+def nsuri(nspfx: str):
+ """Return the namespace URI corresponding to `nspfx`.
+
+ Example:
+
+ >>> nsuri("p")
+ "http://schemas.openxmlformats.org/presentationml/2006/main"
"""
return _nsmap[nspfx]
-def qn(namespace_prefixed_tag):
- """
- Return a Clark-notation qualified tag name corresponding to
- *namespace_prefixed_tag*, a string like 'p:body'. 'qn' stands for
- *qualified name*. As an example, ``qn('p:cSld')`` returns
- ``'{http://schemas.../main}cSld'``.
+def qn(namespace_prefixed_tag: str) -> str:
+ """Return a Clark-notation qualified tag name corresponding to `namespace_prefixed_tag`.
+
+ `namespace_prefixed_tag` is a string like 'p:body'. 'qn' stands for `qualified name`.
+
+ As an example, `qn("p:cSld")` returns:
+ `"{http://schemas.openxmlformats.org/drawingml/2006/main}cSld"`.
"""
nsptag = NamespacePrefixedTag(namespace_prefixed_tag)
return nsptag.clark_name
diff --git a/src/pptx/oxml/presentation.py b/src/pptx/oxml/presentation.py
index 17616cb4f..12c6751f1 100644
--- a/src/pptx/oxml/presentation.py
+++ b/src/pptx/oxml/presentation.py
@@ -1,78 +1,91 @@
-# encoding: utf-8
+"""Custom element classes for presentation-related XML elements."""
-"""
-Custom element classes for presentation-related XML elements.
-"""
+from __future__ import annotations
-from __future__ import absolute_import, division, print_function, unicode_literals
+from typing import TYPE_CHECKING, Callable
-from .simpletypes import ST_SlideId, ST_SlideSizeCoordinate, XsdString
-from .xmlchemy import BaseOxmlElement, RequiredAttribute, ZeroOrOne, ZeroOrMore
+from pptx.oxml.simpletypes import ST_SlideId, ST_SlideSizeCoordinate, XsdString
+from pptx.oxml.xmlchemy import BaseOxmlElement, RequiredAttribute, ZeroOrMore, ZeroOrOne
+if TYPE_CHECKING:
+ from pptx.util import Length
-class CT_Presentation(BaseOxmlElement):
- """
- ```` element, root of the Presentation part stored as
- ``/ppt/presentation.xml``.
- """
- sldMasterIdLst = ZeroOrOne(
- "p:sldMasterIdLst",
- successors=(
- "p:notesMasterIdLst",
- "p:handoutMasterIdLst",
- "p:sldIdLst",
- "p:sldSz",
- "p:notesSz",
- ),
+class CT_Presentation(BaseOxmlElement):
+ """`p:presentation` element, root of the Presentation part stored as `/ppt/presentation.xml`."""
+
+ get_or_add_sldSz: Callable[[], CT_SlideSize]
+ get_or_add_sldIdLst: Callable[[], CT_SlideIdList]
+ get_or_add_sldMasterIdLst: Callable[[], CT_SlideMasterIdList]
+
+ sldMasterIdLst: CT_SlideMasterIdList | None = (
+ ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "p:sldMasterIdLst",
+ successors=(
+ "p:notesMasterIdLst",
+ "p:handoutMasterIdLst",
+ "p:sldIdLst",
+ "p:sldSz",
+ "p:notesSz",
+ ),
+ )
+ )
+ sldIdLst: CT_SlideIdList | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "p:sldIdLst", successors=("p:sldSz", "p:notesSz")
+ )
+ sldSz: CT_SlideSize | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "p:sldSz", successors=("p:notesSz",)
)
- sldIdLst = ZeroOrOne("p:sldIdLst", successors=("p:sldSz", "p:notesSz"))
- sldSz = ZeroOrOne("p:sldSz", successors=("p:notesSz",))
class CT_SlideId(BaseOxmlElement):
- """
- ```` element, direct child of that contains an rId
- reference to a slide in the presentation.
+ """`p:sldId` element.
+
+ Direct child of `p:sldIdLst` that contains an `rId` reference to a slide in the presentation.
"""
- id = RequiredAttribute("id", ST_SlideId)
- rId = RequiredAttribute("r:id", XsdString)
+ id: int = RequiredAttribute("id", ST_SlideId) # pyright: ignore[reportAssignmentType]
+ rId: str = RequiredAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
class CT_SlideIdList(BaseOxmlElement):
+ """`p:sldIdLst` element.
+
+ Direct child of that contains a list of the slide parts in the presentation.
"""
- ```` element, direct child of that contains
- a list of the slide parts in the presentation.
- """
+ sldId_lst: list[CT_SlideId]
+
+ _add_sldId: Callable[..., CT_SlideId]
sldId = ZeroOrMore("p:sldId")
- def add_sldId(self, rId):
- """
- Return a reference to a newly created child element having
- its r:id attribute set to *rId*.
+ def add_sldId(self, rId: str) -> CT_SlideId:
+ """Create and return a reference to a new `p:sldId` child element.
+
+ The new `p:sldId` element has its r:id attribute set to `rId`.
"""
return self._add_sldId(id=self._next_id, rId=rId)
@property
def _next_id(self):
- """
- Return the next available slide ID as an int. Valid slide IDs start
- at 256. The next integer value greater than the max value in use is
- chosen, which minimizes that chance of reusing the id of a deleted
- slide.
+ """The next available slide ID as an `int`.
+
+ Valid slide IDs start at 256. The next integer value greater than the max value in use is
+ chosen, which minimizes that chance of reusing the id of a deleted slide.
"""
id_str_lst = self.xpath("./p:sldId/@id")
return max([255] + [int(id_str) for id_str in id_str_lst]) + 1
class CT_SlideMasterIdList(BaseOxmlElement):
- """
- ```` element, child of ```` containing
- references to the slide masters that belong to the presentation.
+ """`p:sldMasterIdLst` element.
+
+ Child of `p:presentation` containing references to the slide masters that belong to the
+ presentation.
"""
+ sldMasterId_lst: list[CT_SlideMasterIdListEntry]
+
sldMasterId = ZeroOrMore("p:sldMasterId")
@@ -82,14 +95,19 @@ class CT_SlideMasterIdListEntry(BaseOxmlElement):
a reference to a slide master.
"""
- rId = RequiredAttribute("r:id", XsdString)
+ rId: str = RequiredAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
class CT_SlideSize(BaseOxmlElement):
- """
- ```` element, direct child of that contains the
- width and height of slides in the presentation.
+ """`p:sldSz` element.
+
+ Direct child of that contains the width and height of slides in the
+ presentation.
"""
- cx = RequiredAttribute("cx", ST_SlideSizeCoordinate)
- cy = RequiredAttribute("cy", ST_SlideSizeCoordinate)
+ cx: Length = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "cx", ST_SlideSizeCoordinate
+ )
+ cy: Length = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "cy", ST_SlideSizeCoordinate
+ )
diff --git a/src/pptx/oxml/shapes/__init__.py b/src/pptx/oxml/shapes/__init__.py
index e69de29bb..37f8ef60e 100644
--- a/src/pptx/oxml/shapes/__init__.py
+++ b/src/pptx/oxml/shapes/__init__.py
@@ -0,0 +1,19 @@
+"""Base shape-related objects such as BaseShape."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from typing_extensions import TypeAlias
+
+ from pptx.oxml.shapes.autoshape import CT_Shape
+ from pptx.oxml.shapes.connector import CT_Connector
+ from pptx.oxml.shapes.graphfrm import CT_GraphicalObjectFrame
+ from pptx.oxml.shapes.groupshape import CT_GroupShape
+ from pptx.oxml.shapes.picture import CT_Picture
+
+
+ShapeElement: TypeAlias = (
+ "CT_Connector | CT_GraphicalObjectFrame | CT_GroupShape | CT_Picture | CT_Shape"
+)
diff --git a/src/pptx/oxml/shapes/autoshape.py b/src/pptx/oxml/shapes/autoshape.py
index 3da31d132..5d78f624f 100644
--- a/src/pptx/oxml/shapes/autoshape.py
+++ b/src/pptx/oxml/shapes/autoshape.py
@@ -1,10 +1,10 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
-"""
-lxml custom element classes for shape-related XML elements.
-"""
+"""lxml custom element classes for shape-related XML elements."""
-from __future__ import absolute_import
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Callable, cast
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE, PP_PLACEHOLDER
from pptx.oxml import parse_xml
@@ -22,69 +22,91 @@
OneAndOnlyOne,
OptionalAttribute,
RequiredAttribute,
- ZeroOrOne,
ZeroOrMore,
+ ZeroOrOne,
)
+if TYPE_CHECKING:
+ from pptx.oxml.shapes.shared import (
+ CT_ApplicationNonVisualDrawingProps,
+ CT_NonVisualDrawingProps,
+ CT_ShapeProperties,
+ )
+ from pptx.util import Length
+
class CT_AdjPoint2D(BaseOxmlElement):
"""`a:pt` custom element class."""
- x = RequiredAttribute("x", ST_Coordinate)
- y = RequiredAttribute("y", ST_Coordinate)
+ x: Length = RequiredAttribute("x", ST_Coordinate) # pyright: ignore[reportAssignmentType]
+ y: Length = RequiredAttribute("y", ST_Coordinate) # pyright: ignore[reportAssignmentType]
class CT_CustomGeometry2D(BaseOxmlElement):
"""`a:custGeom` custom element class."""
+ get_or_add_pathLst: Callable[[], CT_Path2DList]
+
_tag_seq = ("a:avLst", "a:gdLst", "a:ahLst", "a:cxnLst", "a:rect", "a:pathLst")
- pathLst = ZeroOrOne("a:pathLst", successors=_tag_seq[6:])
+ pathLst: CT_Path2DList | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:pathLst", successors=_tag_seq[6:]
+ )
class CT_GeomGuide(BaseOxmlElement):
- """
- ```` custom element class, defining a "guide", corresponding to
- a yellow diamond-shaped handle on an autoshape.
+ """`a:gd` custom element class.
+
+ Defines a "guide", corresponding to a yellow diamond-shaped handle on an autoshape.
"""
- name = RequiredAttribute("name", XsdString)
- fmla = RequiredAttribute("fmla", XsdString)
+ name: str = RequiredAttribute("name", XsdString) # pyright: ignore[reportAssignmentType]
+ fmla: str = RequiredAttribute("fmla", XsdString) # pyright: ignore[reportAssignmentType]
class CT_GeomGuideList(BaseOxmlElement):
- """
- ```` custom element class
- """
+ """`a:avLst` custom element class."""
+
+ _add_gd: Callable[[], CT_GeomGuide]
+
+ gd_lst: list[CT_GeomGuide]
gd = ZeroOrMore("a:gd")
class CT_NonVisualDrawingShapeProps(BaseShapeElement):
- """
- ```` custom element class
- """
+ """`p:cNvSpPr` custom element class."""
spLocks = ZeroOrOne("a:spLocks")
- txBox = OptionalAttribute("txBox", XsdBoolean)
+ txBox: bool | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "txBox", XsdBoolean
+ )
class CT_Path2D(BaseOxmlElement):
"""`a:path` custom element class."""
+ _add_close: Callable[[], CT_Path2DClose]
+ _add_lnTo: Callable[[], CT_Path2DLineTo]
+ _add_moveTo: Callable[[], CT_Path2DMoveTo]
+
close = ZeroOrMore("a:close", successors=())
lnTo = ZeroOrMore("a:lnTo", successors=())
moveTo = ZeroOrMore("a:moveTo", successors=())
- w = OptionalAttribute("w", ST_PositiveCoordinate)
- h = OptionalAttribute("h", ST_PositiveCoordinate)
-
- def add_close(self):
+ w: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "w", ST_PositiveCoordinate
+ )
+ h: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "h", ST_PositiveCoordinate
+ )
+
+ def add_close(self) -> CT_Path2DClose:
"""Return a newly created `a:close` element.
The new `a:close` element is appended to this `a:path` element.
"""
return self._add_close()
- def add_lnTo(self, x, y):
+ def add_lnTo(self, x: Length, y: Length) -> CT_Path2DLineTo:
"""Return a newly created `a:lnTo` subtree with end point *(x, y)*.
The new `a:lnTo` element is appended to this `a:path` element.
@@ -94,8 +116,8 @@ def add_lnTo(self, x, y):
pt.x, pt.y = x, y
return lnTo
- def add_moveTo(self, x, y):
- """Return a newly created `a:moveTo` subtree with point *(x, y)*.
+ def add_moveTo(self, x: Length, y: Length):
+ """Return a newly created `a:moveTo` subtree with point `(x, y)`.
The new `a:moveTo` element is appended to this `a:path` element.
"""
@@ -112,15 +134,19 @@ class CT_Path2DClose(BaseOxmlElement):
class CT_Path2DLineTo(BaseOxmlElement):
"""`a:lnTo` custom element class."""
+ _add_pt: Callable[[], CT_AdjPoint2D]
+
pt = ZeroOrOne("a:pt", successors=())
class CT_Path2DList(BaseOxmlElement):
"""`a:pathLst` custom element class."""
+ _add_path: Callable[[], CT_Path2D]
+
path = ZeroOrMore("a:path", successors=())
- def add_path(self, w, h):
+ def add_path(self, w: Length, h: Length):
"""Return a newly created `a:path` child element."""
path = self._add_path()
path.w, path.h = w, h
@@ -130,33 +156,32 @@ def add_path(self, w, h):
class CT_Path2DMoveTo(BaseOxmlElement):
"""`a:moveTo` custom element class."""
+ _add_pt: Callable[[], CT_AdjPoint2D]
+
pt = ZeroOrOne("a:pt", successors=())
class CT_PresetGeometry2D(BaseOxmlElement):
- """
- custom element class
- """
+ """`a:prstGeom` custom element class."""
- avLst = ZeroOrOne("a:avLst")
- prst = RequiredAttribute("prst", MSO_AUTO_SHAPE_TYPE)
+ _add_avLst: Callable[[], CT_GeomGuideList]
+ _remove_avLst: Callable[[], None]
+
+ avLst: CT_GeomGuideList | None = ZeroOrOne("a:avLst") # pyright: ignore[reportAssignmentType]
+ prst: MSO_AUTO_SHAPE_TYPE = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "prst", MSO_AUTO_SHAPE_TYPE
+ )
@property
- def gd_lst(self):
- """
- Sequence containing the ``gd`` element children of ````
- child element, empty if none are present.
- """
+ def gd_lst(self) -> list[CT_GeomGuide]:
+ """Sequence of `a:gd` element children of `a:avLst`. Empty if none are present."""
avLst = self.avLst
if avLst is None:
return []
return avLst.gd_lst
- def rewrite_guides(self, guides):
- """
- Remove any ```` element children of ```` and replace
- them with ones having (name, val) in *guides*.
- """
+ def rewrite_guides(self, guides: list[tuple[str, int]]):
+ """Replace any `a:gd` element children of `a:avLst` with ones forme from `guides`."""
self._remove_avLst()
avLst = self._add_avLst()
for name, val in guides:
@@ -166,16 +191,15 @@ def rewrite_guides(self, guides):
class CT_Shape(BaseShapeElement):
- """
- ```` custom element class
- """
+ """`p:sp` custom element class."""
+
+ get_or_add_txBody: Callable[[], CT_TextBody]
- nvSpPr = OneAndOnlyOne("p:nvSpPr")
- spPr = OneAndOnlyOne("p:spPr")
- txBody = ZeroOrOne("p:txBody", successors=("p:extLst",))
+ nvSpPr: CT_ShapeNonVisual = OneAndOnlyOne("p:nvSpPr") # pyright: ignore[reportAssignmentType]
+ spPr: CT_ShapeProperties = OneAndOnlyOne("p:spPr") # pyright: ignore[reportAssignmentType]
+ txBody: CT_TextBody | None = ZeroOrOne("p:txBody", successors=("p:extLst",)) # pyright: ignore
- def add_path(self, w, h):
- """Reference to `a:custGeom` descendant or |None| if not present."""
+ def add_path(self, w: Length, h: Length) -> CT_Path2D:
custGeom = self.spPr.custGeom
if custGeom is None:
raise ValueError("shape must be freeform")
@@ -183,9 +207,7 @@ def add_path(self, w, h):
return pathLst.add_path(w=w, h=h)
def get_or_add_ln(self):
- """
- Return the grandchild element, newly added if not present.
- """
+ """Return the `a:ln` grandchild element, newly added if not present."""
return self.spPr.get_or_add_ln()
@property
@@ -199,120 +221,36 @@ def has_custom_geometry(self):
@property
def is_autoshape(self):
- """
- True if this shape is an auto shape. A shape is an auto shape if it
- has a ```` element and does not have a txBox="1" attribute
- on cNvSpPr.
+ """True if this shape is an auto shape.
+
+ A shape is an auto shape if it has a `a:prstGeom` element and does not have a txBox="1"
+ attribute on cNvSpPr.
"""
prstGeom = self.prstGeom
if prstGeom is None:
return False
- if self.nvSpPr.cNvSpPr.txBox is True:
- return False
- return True
+ return self.nvSpPr.cNvSpPr.txBox is not True
@property
def is_textbox(self):
+ """True if this shape is a text box.
+
+ A shape is a text box if it has a `txBox` attribute on cNvSpPr that resolves to |True|.
+ The default when the txBox attribute is missing is |False|.
"""
- True if this shape is a text box. A shape is a text box if it has a
- ``txBox`` attribute on cNvSpPr that resolves to |True|. The default
- when the txBox attribute is missing is |False|.
- """
- if self.nvSpPr.cNvSpPr.txBox is True:
- return True
- return False
+ return self.nvSpPr.cNvSpPr.txBox is True
@property
def ln(self):
- """
- ```` grand-child element or |None| if not present
- """
+ """`a:ln` grand-child element or |None| if not present."""
return self.spPr.ln
@staticmethod
- def new_autoshape_sp(id_, name, prst, left, top, width, height):
- """
- Return a new ```` element tree configured as a base auto shape.
- """
- tmpl = CT_Shape._autoshape_sp_tmpl()
- xml = tmpl % (id_, name, left, top, width, height, prst)
- sp = parse_xml(xml)
- return sp
-
- @staticmethod
- def new_freeform_sp(shape_id, name, x, y, cx, cy):
- """Return new `p:sp` element tree configured as freeform shape.
-
- The returned shape has a `a:custGeom` subtree but no paths in its
- path list.
- """
- tmpl = CT_Shape._freeform_sp_tmpl()
- xml = tmpl % (shape_id, name, x, y, cx, cy)
- sp = parse_xml(xml)
- return sp
-
- @staticmethod
- def new_placeholder_sp(id_, name, ph_type, orient, sz, idx):
- """
- Return a new ```` element tree configured as a placeholder
- shape.
- """
- tmpl = CT_Shape._ph_sp_tmpl()
- xml = tmpl % (id_, name)
- sp = parse_xml(xml)
-
- ph = sp.nvSpPr.nvPr.get_or_add_ph()
- ph.type = ph_type
- ph.idx = idx
- ph.orient = orient
- ph.sz = sz
-
- placeholder_types_that_have_a_text_frame = (
- PP_PLACEHOLDER.TITLE,
- PP_PLACEHOLDER.CENTER_TITLE,
- PP_PLACEHOLDER.SUBTITLE,
- PP_PLACEHOLDER.BODY,
- PP_PLACEHOLDER.OBJECT,
- )
-
- if ph_type in placeholder_types_that_have_a_text_frame:
- sp.append(CT_TextBody.new())
-
- return sp
-
- @staticmethod
- def new_textbox_sp(id_, name, left, top, width, height):
- """
- Return a new ```` element tree configured as a base textbox
- shape.
- """
- tmpl = CT_Shape._textbox_sp_tmpl()
- xml = tmpl % (id_, name, left, top, width, height)
- sp = parse_xml(xml)
- return sp
-
- @property
- def prst(self):
- """
- Value of ``prst`` attribute of ```` element or |None| if
- not present.
- """
- prstGeom = self.prstGeom
- if prstGeom is None:
- return None
- return prstGeom.prst
-
- @property
- def prstGeom(self):
- """
- Reference to ```` child element or |None| if this shape
- doesn't have one, for example, if it's a placeholder shape.
- """
- return self.spPr.prstGeom
-
- @staticmethod
- def _autoshape_sp_tmpl():
- return (
+ def new_autoshape_sp(
+ id_: int, name: str, prst: str, left: int, top: int, width: int, height: int
+ ) -> CT_Shape:
+ """Return a new `p:sp` element tree configured as a base auto shape."""
+ xml = (
"\n"
" \n"
' \n'
@@ -350,11 +288,17 @@ def _autoshape_sp_tmpl():
" \n"
" \n"
"" % (nsdecls("a", "p"), "%d", "%s", "%d", "%d", "%d", "%d", "%s")
- )
+ ) % (id_, name, left, top, width, height, prst)
+ return cast(CT_Shape, parse_xml(xml))
@staticmethod
- def _freeform_sp_tmpl():
- return (
+ def new_freeform_sp(shape_id: int, name: str, x: int, y: int, cx: int, cy: int):
+ """Return new `p:sp` element tree configured as freeform shape.
+
+ The returned shape has a `a:custGeom` subtree but no paths in its
+ path list.
+ """
+ xml = (
"\n"
" \n"
' \n'
@@ -397,26 +341,76 @@ def _freeform_sp_tmpl():
" \n"
" \n"
"" % (nsdecls("a", "p"), "%d", "%s", "%d", "%d", "%d", "%d")
+ ) % (shape_id, name, x, y, cx, cy)
+ return cast(CT_Shape, parse_xml(xml))
+
+ @staticmethod
+ def new_placeholder_sp(
+ id_: int, name: str, ph_type: PP_PLACEHOLDER, orient: str, sz, idx
+ ) -> CT_Shape:
+ """Return a new `p:sp` element tree configured as a placeholder shape."""
+ sp = cast(
+ CT_Shape,
+ parse_xml(
+ f"\n"
+ f" \n"
+ f' \n'
+ f" \n"
+ f' \n'
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f""
+ ),
)
- def _new_txBody(self):
- return CT_TextBody.new_p_txBody()
+ ph = sp.nvSpPr.nvPr.get_or_add_ph()
+ ph.type = ph_type
+ ph.idx = idx
+ ph.orient = orient
+ ph.sz = sz
- @staticmethod
- def _ph_sp_tmpl():
- return (
- "\n"
- " \n"
- ' \n'
- " \n"
- ' \n'
- " \n"
- " \n"
- " \n"
- " \n"
- "" % (nsdecls("a", "p"), "%d", "%s")
+ placeholder_types_that_have_a_text_frame = (
+ PP_PLACEHOLDER.TITLE,
+ PP_PLACEHOLDER.CENTER_TITLE,
+ PP_PLACEHOLDER.SUBTITLE,
+ PP_PLACEHOLDER.BODY,
+ PP_PLACEHOLDER.OBJECT,
)
+ if ph_type in placeholder_types_that_have_a_text_frame:
+ sp.append(CT_TextBody.new())
+
+ return sp
+
+ @staticmethod
+ def new_textbox_sp(id_, name, left, top, width, height):
+ """Return a new `p:sp` element tree configured as a base textbox shape."""
+ tmpl = CT_Shape._textbox_sp_tmpl()
+ xml = tmpl % (id_, name, left, top, width, height)
+ sp = parse_xml(xml)
+ return sp
+
+ @property
+ def prst(self):
+ """Value of `prst` attribute of `a:prstGeom` element or |None| if not present."""
+ prstGeom = self.prstGeom
+ if prstGeom is None:
+ return None
+ return prstGeom.prst
+
+ @property
+ def prstGeom(self) -> CT_PresetGeometry2D:
+ """Reference to `a:prstGeom` child element.
+
+ |None| if this shape doesn't have one, for example, if it's a placeholder shape.
+ """
+ return self.spPr.prstGeom
+
+ def _new_txBody(self):
+ return CT_TextBody.new_p_txBody()
+
@staticmethod
def _textbox_sp_tmpl():
return (
@@ -448,10 +442,14 @@ def _textbox_sp_tmpl():
class CT_ShapeNonVisual(BaseShapeElement):
- """
- ```` custom element class
- """
-
- cNvPr = OneAndOnlyOne("p:cNvPr")
- cNvSpPr = OneAndOnlyOne("p:cNvSpPr")
- nvPr = OneAndOnlyOne("p:nvPr")
+ """`p:nvSpPr` custom element class."""
+
+ cNvPr: CT_NonVisualDrawingProps = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
+ "p:cNvPr"
+ )
+ cNvSpPr: CT_NonVisualDrawingShapeProps = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
+ "p:cNvSpPr"
+ )
+ nvPr: CT_ApplicationNonVisualDrawingProps = ( # pyright: ignore[reportAssignmentType]
+ OneAndOnlyOne("p:nvPr")
+ )
diff --git a/src/pptx/oxml/shapes/connector.py b/src/pptx/oxml/shapes/connector.py
index ebbe1045f..91261f780 100644
--- a/src/pptx/oxml/shapes/connector.py
+++ b/src/pptx/oxml/shapes/connector.py
@@ -1,22 +1,23 @@
-# encoding: utf-8
+"""lxml custom element classes for XML elements related to the Connector shape."""
-"""
-lxml custom element classes for shape-related XML elements.
-"""
+from __future__ import annotations
-from __future__ import absolute_import
+from typing import TYPE_CHECKING, cast
-from .. import parse_xml
-from ..ns import nsdecls
-from .shared import BaseShapeElement
-from ..simpletypes import ST_DrawingElementId, XsdUnsignedInt
-from ..xmlchemy import BaseOxmlElement, OneAndOnlyOne, RequiredAttribute, ZeroOrOne
+from pptx.oxml import parse_xml
+from pptx.oxml.ns import nsdecls
+from pptx.oxml.shapes.shared import BaseShapeElement
+from pptx.oxml.simpletypes import ST_DrawingElementId, XsdUnsignedInt
+from pptx.oxml.xmlchemy import BaseOxmlElement, OneAndOnlyOne, RequiredAttribute, ZeroOrOne
+
+if TYPE_CHECKING:
+ from pptx.oxml.shapes.shared import CT_ShapeProperties
class CT_Connection(BaseShapeElement):
- """
- A `a:stCxn` or `a:endCxn` element specifying a connection between
- an end-point of a connector and a shape connection point.
+ """A `a:stCxn` or `a:endCxn` element.
+
+ Specifies a connection between an end-point of a connector and a shape connection point.
"""
id = RequiredAttribute("id", ST_DrawingElementId)
@@ -24,77 +25,68 @@ class CT_Connection(BaseShapeElement):
class CT_Connector(BaseShapeElement):
- """
- A line/connector shape ```` element
- """
+ """A line/connector shape `p:cxnSp` element"""
_tag_seq = ("p:nvCxnSpPr", "p:spPr", "p:style", "p:extLst")
nvCxnSpPr = OneAndOnlyOne("p:nvCxnSpPr")
- spPr = OneAndOnlyOne("p:spPr")
+ spPr: CT_ShapeProperties = OneAndOnlyOne("p:spPr") # pyright: ignore[reportAssignmentType]
del _tag_seq
@classmethod
- def new_cxnSp(cls, id_, name, prst, x, y, cx, cy, flipH, flipV):
- """
- Return a new ```` element tree configured as a base
- connector.
- """
- tmpl = cls._cxnSp_tmpl()
+ def new_cxnSp(
+ cls,
+ id_: int,
+ name: str,
+ prst: str,
+ x: int,
+ y: int,
+ cx: int,
+ cy: int,
+ flipH: bool,
+ flipV: bool,
+ ) -> CT_Connector:
+ """Return a new `p:cxnSp` element tree configured as a base connector."""
flip = (' flipH="1"' if flipH else "") + (' flipV="1"' if flipV else "")
- xml = tmpl.format(
- **{
- "nsdecls": nsdecls("a", "p"),
- "id": id_,
- "name": name,
- "x": x,
- "y": y,
- "cx": cx,
- "cy": cy,
- "prst": prst,
- "flip": flip,
- }
- )
- return parse_xml(xml)
-
- @staticmethod
- def _cxnSp_tmpl():
- return (
- "\n"
- " \n"
- ' \n'
- " \n"
- " \n"
- " \n"
- " \n"
- " \n"
- ' \n'
- ' \n'
- " \n"
- ' \n'
- " \n"
- " \n"
- " \n"
- " \n"
- ' \n'
- ' \n'
- " \n"
- ' \n'
- ' \n'
- " \n"
- ' \n'
- ' \n'
- " \n"
- ' \n'
- ' \n'
- " \n"
- " \n"
- ""
+ return cast(
+ CT_Connector,
+ parse_xml(
+ f"\n"
+ f" \n"
+ f' \n'
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f' \n'
+ f' \n'
+ f" \n"
+ f' \n'
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f' \n'
+ f' \n'
+ f" \n"
+ f' \n'
+ f' \n'
+ f" \n"
+ f' \n'
+ f' \n'
+ f" \n"
+ f' \n'
+ f' \n'
+ f" \n"
+ f" \n"
+ f""
+ ),
)
class CT_ConnectorNonVisual(BaseOxmlElement):
"""
- ```` element, container for the non-visual properties of
+ `p:nvCxnSpPr` element, container for the non-visual properties of
a connector, such as name, id, etc.
"""
diff --git a/src/pptx/oxml/shapes/graphfrm.py b/src/pptx/oxml/shapes/graphfrm.py
index 6f65da7f4..cf32377c2 100644
--- a/src/pptx/oxml/shapes/graphfrm.py
+++ b/src/pptx/oxml/shapes/graphfrm.py
@@ -1,7 +1,9 @@
-# encoding: utf-8
-
"""lxml custom element class for CT_GraphicalObjectFrame XML element."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, cast
+
from pptx.oxml import parse_xml
from pptx.oxml.chart.chart import CT_Chart
from pptx.oxml.ns import nsdecls
@@ -21,159 +23,165 @@
GRAPHIC_DATA_URI_TABLE,
)
+if TYPE_CHECKING:
+ from pptx.oxml.shapes.shared import (
+ CT_ApplicationNonVisualDrawingProps,
+ CT_NonVisualDrawingProps,
+ CT_Transform2D,
+ )
+
class CT_GraphicalObject(BaseOxmlElement):
- """
- ```` element, which is the container for the reference to or
- definition of the framed graphical object (table, chart, etc.).
+ """`a:graphic` element.
+
+ The container for the reference to or definition of the framed graphical object (table, chart,
+ etc.).
"""
- graphicData = OneAndOnlyOne("a:graphicData")
+ graphicData: CT_GraphicalObjectData = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
+ "a:graphicData"
+ )
@property
- def chart(self):
- """
- The ```` grandchild element, or |None| if not present.
- """
+ def chart(self) -> CT_Chart | None:
+ """The `c:chart` grandchild element, or |None| if not present."""
return self.graphicData.chart
class CT_GraphicalObjectData(BaseShapeElement):
- """
- ```` element, the direct container for a table, a chart,
- or another graphical object.
+ """`p:graphicData` element.
+
+ The direct container for a table, a chart, or another graphical object.
"""
- chart = ZeroOrOne("c:chart")
- tbl = ZeroOrOne("a:tbl")
- uri = RequiredAttribute("uri", XsdString)
+ chart: CT_Chart | None = ZeroOrOne("c:chart") # pyright: ignore[reportAssignmentType]
+ tbl: CT_Table | None = ZeroOrOne("a:tbl") # pyright: ignore[reportAssignmentType]
+ uri: str = RequiredAttribute("uri", XsdString) # pyright: ignore[reportAssignmentType]
@property
- def blob_rId(self):
- """Optional "r:id" attribute value of `` descendent element.
+ def blob_rId(self) -> str | None:
+ """Optional `r:id` attribute value of `p:oleObj` descendent element.
- This value is `None` when this `p:graphicData` element does not enclose an OLE
- object. This value could also be `None` if an enclosed OLE object does not
- specify this attribute (it is specified optional in the schema) but so far, all
- OLE objects we've encountered specify this value.
+ This value is `None` when this `p:graphicData` element does not enclose an OLE object.
+ This value could also be `None` if an enclosed OLE object does not specify this attribute
+ (it is specified optional in the schema) but so far, all OLE objects we've encountered
+ specify this value.
"""
return None if self._oleObj is None else self._oleObj.rId
@property
- def is_embedded_ole_obj(self):
+ def is_embedded_ole_obj(self) -> bool | None:
"""Optional boolean indicating an embedded OLE object.
- Returns `None` when this `p:graphicData` element does not enclose an OLE object.
- `True` indicates an embedded OLE object and `False` indicates a linked OLE
- object.
+ Returns `None` when this `p:graphicData` element does not enclose an OLE object. `True`
+ indicates an embedded OLE object and `False` indicates a linked OLE object.
"""
return None if self._oleObj is None else self._oleObj.is_embedded
@property
- def progId(self):
- """Optional str value of "progId" attribute of `` descendent.
+ def progId(self) -> str | None:
+ """Optional str value of "progId" attribute of `p:oleObj` descendent.
- This value identifies the "type" of the embedded object in terms of the
- application used to open it.
+ This value identifies the "type" of the embedded object in terms of the application used
+ to open it.
- This value is `None` when this `p:graphicData` element does not enclose an OLE
- object. This could also be `None` if an enclosed OLE object does not specify
- this attribute (it is specified optional in the schema) but so far, all OLE
- objects we've encountered specify this value.
+ This value is `None` when this `p:graphicData` element does not enclose an OLE object.
+ This could also be `None` if an enclosed OLE object does not specify this attribute (it is
+ specified optional in the schema) but so far, all OLE objects we've encountered specify
+ this value.
"""
return None if self._oleObj is None else self._oleObj.progId
@property
- def showAsIcon(self):
+ def showAsIcon(self) -> bool | None:
"""Optional value of "showAsIcon" attribute value of `p:oleObj` descendent.
- This value is `None` when this `p:graphicData` element does not enclose an OLE
- object. It is False when the `showAsIcon` attribute is omitted on the `p:oleObj`
- element.
+ This value is `None` when this `p:graphicData` element does not enclose an OLE object. It
+ is False when the `showAsIcon` attribute is omitted on the `p:oleObj` element.
"""
return None if self._oleObj is None else self._oleObj.showAsIcon
@property
- def _oleObj(self):
- """Optional `` element contained in this `p:graphicData' element.
-
- Returns `None` when this graphic-data element does not enclose an OLE object.
- Note that this returns the last `p:oleObj` element found. There can be more
- than one `p:oleObj` element because an `` element may
- appear as the child of `p:graphicData` and that alternate-content subtree can
- contain multiple compatibility choices. The last one should suit best for
- reading purposes because it contains the lowest common denominator.
+ def _oleObj(self) -> CT_OleObject | None:
+ """Optional `p:oleObj` element contained in this `p:graphicData' element.
+
+ Returns `None` when this graphic-data element does not enclose an OLE object. Note that
+ this returns the last `p:oleObj` element found. There can be more than one `p:oleObj`
+ element because an `mc.AlternateContent` element may appear as the child of
+ `p:graphicData` and that alternate-content subtree can contain multiple compatibility
+ choices. The last one should suit best for reading purposes because it contains the lowest
+ common denominator.
"""
- oleObjs = self.xpath(".//p:oleObj")
+ oleObjs = cast(list[CT_OleObject], self.xpath(".//p:oleObj"))
return oleObjs[-1] if oleObjs else None
class CT_GraphicalObjectFrame(BaseShapeElement):
- """
- ```` element, which is a container for a table, a chart,
- or another graphical object.
+ """`p:graphicFrame` element.
+
+ A container for a table, a chart, or another graphical object.
"""
- nvGraphicFramePr = OneAndOnlyOne("p:nvGraphicFramePr")
- xfrm = OneAndOnlyOne("p:xfrm")
- graphic = OneAndOnlyOne("a:graphic")
+ nvGraphicFramePr: CT_GraphicalObjectFrameNonVisual = ( # pyright: ignore[reportAssignmentType]
+ OneAndOnlyOne("p:nvGraphicFramePr")
+ )
+ xfrm: CT_Transform2D = OneAndOnlyOne("p:xfrm") # pyright: ignore
+ graphic: CT_GraphicalObject = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
+ "a:graphic"
+ )
@property
- def chart(self):
- """
- The ```` great-grandchild element, or |None| if not present.
- """
+ def chart(self) -> CT_Chart | None:
+ """The `c:chart` great-grandchild element, or |None| if not present."""
return self.graphic.chart
@property
- def chart_rId(self):
- """
- The ``rId`` attribute of the ```` great-grandchild element,
- or |None| if not present.
+ def chart_rId(self) -> str | None:
+ """The `rId` attribute of the `c:chart` great-grandchild element.
+
+ |None| if not present.
"""
chart = self.chart
if chart is None:
return None
return chart.rId
- def get_or_add_xfrm(self):
- """
- Return the required ```` child element. Overrides version on
- BaseShapeElement.
+ def get_or_add_xfrm(self) -> CT_Transform2D:
+ """Return the required `p:xfrm` child element.
+
+ Overrides version on BaseShapeElement.
"""
return self.xfrm
@property
- def graphicData(self):
- """` grandchild of this graphic-frame element."""
+ def graphicData(self) -> CT_GraphicalObjectData:
+ """`a:graphicData` grandchild of this graphic-frame element."""
return self.graphic.graphicData
@property
- def graphicData_uri(self):
- """str value of `uri` attribute of ` grandchild."""
+ def graphicData_uri(self) -> str:
+ """str value of `uri` attribute of `a:graphicData` grandchild."""
return self.graphic.graphicData.uri
@property
- def has_oleobj(self):
- """True for graphicFrame containing an OLE object, False otherwise."""
+ def has_oleobj(self) -> bool:
+ """`True` for graphicFrame containing an OLE object, `False` otherwise."""
return self.graphicData.uri == GRAPHIC_DATA_URI_OLEOBJ
@property
- def is_embedded_ole_obj(self):
+ def is_embedded_ole_obj(self) -> bool | None:
"""Optional boolean indicating an embedded OLE object.
- Returns `None` when this `p:graphicFrame` element does not enclose an OLE
- object. `True` indicates an embedded OLE object and `False` indicates a linked
- OLE object.
+ Returns `None` when this `p:graphicFrame` element does not enclose an OLE object. `True`
+ indicates an embedded OLE object and `False` indicates a linked OLE object.
"""
return self.graphicData.is_embedded_ole_obj
@classmethod
- def new_chart_graphicFrame(cls, id_, name, rId, x, y, cx, cy):
- """
- Return a ```` element tree populated with a chart
- element.
- """
+ def new_chart_graphicFrame(
+ cls, id_: int, name: str, rId: str, x: int, y: int, cx: int, cy: int
+ ) -> CT_GraphicalObjectFrame:
+ """Return a `p:graphicFrame` element tree populated with a chart element."""
graphicFrame = CT_GraphicalObjectFrame.new_graphicFrame(id_, name, x, y, cx, cy)
graphicData = graphicFrame.graphic.graphicData
graphicData.uri = GRAPHIC_DATA_URI_CHART
@@ -181,160 +189,154 @@ def new_chart_graphicFrame(cls, id_, name, rId, x, y, cx, cy):
return graphicFrame
@classmethod
- def new_graphicFrame(cls, id_, name, x, y, cx, cy):
- """
- Return a new ```` element tree suitable for
- containing a table or chart. Note that a graphicFrame element is not
- a valid shape until it contains a graphical object such as a table.
+ def new_graphicFrame(
+ cls, id_: int, name: str, x: int, y: int, cx: int, cy: int
+ ) -> CT_GraphicalObjectFrame:
+ """Return a new `p:graphicFrame` element tree suitable for containing a table or chart.
+
+ Note that a graphicFrame element is not a valid shape until it contains a graphical object
+ such as a table.
"""
- xml = cls._graphicFrame_tmpl() % (id_, name, x, y, cx, cy)
- graphicFrame = parse_xml(xml)
- return graphicFrame
+ return cast(
+ CT_GraphicalObjectFrame,
+ parse_xml(
+ f"\n"
+ f" \n"
+ f' \n'
+ f" \n"
+ f' \n'
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f' \n'
+ f' \n'
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f""
+ ),
+ )
@classmethod
def new_ole_object_graphicFrame(
- cls, id_, name, ole_object_rId, progId, icon_rId, x, y, cx, cy, imgW, imgH
- ):
- """Return newly-created `` for embedded OLE-object.
+ cls,
+ id_: int,
+ name: str,
+ ole_object_rId: str,
+ progId: str,
+ icon_rId: str,
+ x: int,
+ y: int,
+ cx: int,
+ cy: int,
+ imgW: int,
+ imgH: int,
+ ) -> CT_GraphicalObjectFrame:
+ """Return newly-created `p:graphicFrame` for embedded OLE-object.
`ole_object_rId` identifies the relationship to the OLE-object part.
- `progId` is a str identifying the object-type in terms of the application
- (program) used to open it. This becomes an attribute of the same name in the
- `p:oleObj` element.
+ `progId` is a str identifying the object-type in terms of the application (program) used
+ to open it. This becomes an attribute of the same name in the `p:oleObj` element.
- `icon_rId` identifies the relationship to an image part used to display the
- OLE-object as an icon (vs. a preview).
+ `icon_rId` identifies the relationship to an image part used to display the OLE-object as
+ an icon (vs. a preview).
"""
- return parse_xml(
- cls._graphicFrame_xml_for_ole_object(
- id_, name, x, y, cx, cy, ole_object_rId, progId, icon_rId, imgW, imgH
- )
+ return cast(
+ CT_GraphicalObjectFrame,
+ parse_xml(
+ f"\n"
+ f" \n"
+ f' \n'
+ f" \n"
+ f' \n'
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f' \n'
+ f' \n'
+ f" \n"
+ f" \n"
+ f" \n'
+ f' \n'
+ f" \n"
+ f" \n"
+ f" \n"
+ f' \n'
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f' \n'
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f' \n'
+ f' \n'
+ f" \n"
+ f' \n'
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f""
+ ),
)
@classmethod
- def new_table_graphicFrame(cls, id_, name, rows, cols, x, y, cx, cy):
- """
- Return a ```` element tree populated with a table
- element.
- """
+ def new_table_graphicFrame(
+ cls, id_: int, name: str, rows: int, cols: int, x: int, y: int, cx: int, cy: int
+ ) -> CT_GraphicalObjectFrame:
+ """Return a `p:graphicFrame` element tree populated with a table element."""
graphicFrame = cls.new_graphicFrame(id_, name, x, y, cx, cy)
graphicFrame.graphic.graphicData.uri = GRAPHIC_DATA_URI_TABLE
graphicFrame.graphic.graphicData.append(CT_Table.new_tbl(rows, cols, cx, cy))
return graphicFrame
- @classmethod
- def _graphicFrame_tmpl(cls):
- return (
- "\n"
- " \n"
- ' \n'
- " \n"
- ' \n'
- " \n"
- " \n"
- " \n"
- " \n"
- ' \n'
- ' \n'
- " \n"
- " \n"
- " \n"
- " \n"
- ""
- % (nsdecls("a", "p"), "%d", "%s", "%d", "%d", "%d", "%d")
- )
-
- @classmethod
- def _graphicFrame_xml_for_ole_object(
- cls, id_, name, x, y, cx, cy, ole_object_rId, progId, icon_rId, imgW, imgH
- ):
- """str XML for element of an embedded OLE-object shape."""
- return (
- "\n"
- " \n"
- ' \n'
- " \n"
- ' \n'
- " \n"
- " \n"
- " \n"
- " \n"
- ' \n'
- ' \n'
- " \n"
- " \n"
- " \n'
- ' \n'
- " \n"
- " \n"
- " \n"
- ' \n'
- " \n"
- " \n"
- " \n"
- " \n"
- ' \n'
- " \n"
- " \n"
- " \n"
- " \n"
- " \n"
- " \n"
- ' \n'
- ' \n'
- " \n"
- ' \n'
- " \n"
- " \n"
- " \n"
- " \n"
- " \n"
- " \n"
- " \n"
- ""
- ).format(
- nsdecls=nsdecls("a", "p", "r"),
- id_=id_,
- name=name,
- x=x,
- y=y,
- cx=cx,
- cy=cy,
- ole_object_rId=ole_object_rId,
- progId=progId,
- icon_rId=icon_rId,
- imgW=imgW,
- imgH=imgH,
- )
-
class CT_GraphicalObjectFrameNonVisual(BaseOxmlElement):
- """`` element.
+ """`p:nvGraphicFramePr` element.
This contains the non-visual properties of a graphic frame, such as name, id, etc.
"""
- cNvPr = OneAndOnlyOne("p:cNvPr")
- nvPr = OneAndOnlyOne("p:nvPr")
+ cNvPr: CT_NonVisualDrawingProps = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
+ "p:cNvPr"
+ )
+ nvPr: CT_ApplicationNonVisualDrawingProps = ( # pyright: ignore[reportAssignmentType]
+ OneAndOnlyOne("p:nvPr")
+ )
class CT_OleObject(BaseOxmlElement):
- """`` element, container for an OLE object (e.g. Excel file).
+ """`p:oleObj` element, container for an OLE object (e.g. Excel file).
An OLE object can be either linked or embedded (hence the name).
"""
- progId = OptionalAttribute("progId", XsdString)
- rId = OptionalAttribute("r:id", XsdString)
- showAsIcon = OptionalAttribute("showAsIcon", XsdBoolean, default=False)
+ progId: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "progId", XsdString
+ )
+ rId: str | None = OptionalAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
+ showAsIcon: bool = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "showAsIcon", XsdBoolean, default=False
+ )
@property
- def is_embedded(self):
+ def is_embedded(self) -> bool:
"""True when this OLE object is embedded, False when it is linked."""
- return True if len(self.xpath("./p:embed")) > 0 else False
+ return len(self.xpath("./p:embed")) > 0
diff --git a/src/pptx/oxml/shapes/groupshape.py b/src/pptx/oxml/shapes/groupshape.py
index e428bd79e..f62bc6662 100644
--- a/src/pptx/oxml/shapes/groupshape.py
+++ b/src/pptx/oxml/shapes/groupshape.py
@@ -1,8 +1,8 @@
-# encoding: utf-8
-
"""lxml custom element classes for shape-tree-related XML elements."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Callable, Iterator
from pptx.enum.shapes import MSO_CONNECTOR_TYPE
from pptx.oxml import parse_xml
@@ -15,15 +15,21 @@
from pptx.oxml.xmlchemy import BaseOxmlElement, OneAndOnlyOne, ZeroOrOne
from pptx.util import Emu
+if TYPE_CHECKING:
+ from pptx.enum.shapes import PP_PLACEHOLDER
+ from pptx.oxml.shapes import ShapeElement
+ from pptx.oxml.shapes.shared import CT_Transform2D
+
class CT_GroupShape(BaseShapeElement):
- """
- Used for the shape tree (````) element as well as the group
- shape (````) element.
- """
+ """Used for shape tree (`p:spTree`) as well as the group shape (`p:grpSp`) elements."""
- nvGrpSpPr = OneAndOnlyOne("p:nvGrpSpPr")
- grpSpPr = OneAndOnlyOne("p:grpSpPr")
+ nvGrpSpPr: CT_GroupShapeNonVisual = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
+ "p:nvGrpSpPr"
+ )
+ grpSpPr: CT_GroupShapeProperties = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
+ "p:grpSpPr"
+ )
_shape_tags = (
qn("p:sp"),
@@ -34,26 +40,33 @@ class CT_GroupShape(BaseShapeElement):
qn("p:contentPart"),
)
- def add_autoshape(self, id_, name, prst, x, y, cx, cy):
- """
- Append a new ```` shape to the group/shapetree having the
- properties specified in call.
- """
+ def add_autoshape(
+ self, id_: int, name: str, prst: str, x: int, y: int, cx: int, cy: int
+ ) -> CT_Shape:
+ """Return new `p:sp` appended to the group/shapetree with specified attributes."""
sp = CT_Shape.new_autoshape_sp(id_, name, prst, x, y, cx, cy)
self.insert_element_before(sp, "p:extLst")
return sp
- def add_cxnSp(self, id_, name, type_member, x, y, cx, cy, flipH, flipV):
- """
- Append a new ```` shape to the group/shapetree having the
- properties specified in call.
- """
+ def add_cxnSp(
+ self,
+ id_: int,
+ name: str,
+ type_member: MSO_CONNECTOR_TYPE,
+ x: int,
+ y: int,
+ cx: int,
+ cy: int,
+ flipH: bool,
+ flipV: bool,
+ ) -> CT_Connector:
+ """Return new `p:cxnSp` appended to the group/shapetree with the specified attribues."""
prst = MSO_CONNECTOR_TYPE.to_xml(type_member)
cxnSp = CT_Connector.new_cxnSp(id_, name, prst, x, y, cx, cy, flipH, flipV)
self.insert_element_before(cxnSp, "p:extLst")
return cxnSp
- def add_freeform_sp(self, x, y, cx, cy):
+ def add_freeform_sp(self, x: int, y: int, cx: int, cy: int) -> CT_Shape:
"""Append a new freeform `p:sp` with specified position and size."""
shape_id = self._next_shape_id
name = "Freeform %d" % (shape_id - 1,)
@@ -61,7 +74,7 @@ def add_freeform_sp(self, x, y, cx, cy):
self.insert_element_before(sp, "p:extLst")
return sp
- def add_grpSp(self):
+ def add_grpSp(self) -> CT_GroupShape:
"""Return `p:grpSp` element newly appended to this shape tree.
The element contains no sub-shapes, is positioned at (0, 0), and has
@@ -73,40 +86,34 @@ def add_grpSp(self):
self.insert_element_before(grpSp, "p:extLst")
return grpSp
- def add_pic(self, id_, name, desc, rId, x, y, cx, cy):
- """
- Append a ```` shape to the group/shapetree having properties
- as specified in call.
- """
+ def add_pic(
+ self, id_: int, name: str, desc: str, rId: str, x: int, y: int, cx: int, cy: int
+ ) -> CT_Picture:
+ """Append a `p:pic` shape to the group/shapetree having properties as specified in call."""
pic = CT_Picture.new_pic(id_, name, desc, rId, x, y, cx, cy)
self.insert_element_before(pic, "p:extLst")
return pic
- def add_placeholder(self, id_, name, ph_type, orient, sz, idx):
- """
- Append a newly-created placeholder ```` shape having the
- specified placeholder properties.
- """
+ def add_placeholder(
+ self, id_: int, name: str, ph_type: PP_PLACEHOLDER, orient: str, sz: str, idx: int
+ ) -> CT_Shape:
+ """Append a newly-created placeholder `p:sp` shape having the specified properties."""
sp = CT_Shape.new_placeholder_sp(id_, name, ph_type, orient, sz, idx)
self.insert_element_before(sp, "p:extLst")
return sp
- def add_table(self, id_, name, rows, cols, x, y, cx, cy):
- """
- Append a ```` shape containing a table as specified
- in call.
- """
+ def add_table(
+ self, id_: int, name: str, rows: int, cols: int, x: int, y: int, cx: int, cy: int
+ ) -> CT_GraphicalObjectFrame:
+ """Append a `p:graphicFrame` shape containing a table as specified in call."""
graphicFrame = CT_GraphicalObjectFrame.new_table_graphicFrame(
id_, name, rows, cols, x, y, cx, cy
)
self.insert_element_before(graphicFrame, "p:extLst")
return graphicFrame
- def add_textbox(self, id_, name, x, y, cx, cy):
- """
- Append a newly-created textbox ```` shape having the specified
- position and size.
- """
+ def add_textbox(self, id_: int, name: str, x: int, y: int, cx: int, cy: int) -> CT_Shape:
+ """Append a newly-created textbox `p:sp` shape having the specified position and size."""
sp = CT_Shape.new_textbox_sp(id_, name, x, y, cx, cy)
self.insert_element_before(sp, "p:extLst")
return sp
@@ -121,32 +128,27 @@ def chOff(self):
"""Descendent `p:grpSpPr/a:xfrm/a:chOff` element."""
return self.grpSpPr.get_or_add_xfrm().get_or_add_chOff()
- def get_or_add_xfrm(self):
- """
- Return the ```` grandchild element, newly-added if not
- present.
- """
+ def get_or_add_xfrm(self) -> CT_Transform2D:
+ """Return the `a:xfrm` grandchild element, newly-added if not present."""
return self.grpSpPr.get_or_add_xfrm()
def iter_ph_elms(self):
- """
- Generate each placeholder shape child element in document order.
- """
+ """Generate each placeholder shape child element in document order."""
for e in self.iter_shape_elms():
if e.has_ph_elm:
yield e
- def iter_shape_elms(self):
- """
- Generate each child of this ```` element that corresponds
- to a shape, in the sequence they appear in the XML.
+ def iter_shape_elms(self) -> Iterator[ShapeElement]:
+ """Generate each child of this `p:spTree` element that corresponds to a shape.
+
+ Items appear in XML document order.
"""
for elm in self.iterchildren():
if elm.tag in self._shape_tags:
yield elm
@property
- def max_shape_id(self):
+ def max_shape_id(self) -> int:
"""Maximum int value assigned as @id in this slide.
This is generally a shape-id, but ids can be assigned to other
@@ -161,8 +163,8 @@ def max_shape_id(self):
return max(used_ids) if used_ids else 0
@classmethod
- def new_grpSp(cls, id_, name):
- """Return new "loose" `p:grpSp` element having *id_* and *name*."""
+ def new_grpSp(cls, id_: int, name: str) -> CT_GroupShape:
+ """Return new "loose" `p:grpSp` element having `id_` and `name`."""
xml = (
"\n"
" \n"
@@ -183,7 +185,7 @@ def new_grpSp(cls, id_, name):
grpSp = parse_xml(xml)
return grpSp
- def recalculate_extents(self):
+ def recalculate_extents(self) -> None:
"""Adjust x, y, cx, and cy to incorporate all contained shapes.
This would typically be called when a contained shape is added,
@@ -204,14 +206,12 @@ def recalculate_extents(self):
self.getparent().recalculate_extents()
@property
- def xfrm(self):
- """
- The ```` grandchild element or |None| if not found
- """
+ def xfrm(self) -> CT_Transform2D | None:
+ """The `a:xfrm` grandchild element or |None| if not found."""
return self.grpSpPr.xfrm
@property
- def _child_extents(self):
+ def _child_extents(self) -> tuple[int, int, int, int]:
"""(x, y, cx, cy) tuple representing net position and size.
The values are formed as a composite of the contained child shapes.
@@ -234,7 +234,7 @@ def _child_extents(self):
return x, y, cx, cy
@property
- def _next_shape_id(self):
+ def _next_shape_id(self) -> int:
"""Return unique shape id suitable for use with a new shape element.
The returned id is the next available positive integer drawing object
@@ -250,15 +250,15 @@ def _next_shape_id(self):
class CT_GroupShapeNonVisual(BaseShapeElement):
- """
- ```` element.
- """
+ """`p:nvGrpSpPr` element."""
cNvPr = OneAndOnlyOne("p:cNvPr")
class CT_GroupShapeProperties(BaseOxmlElement):
- """p:grpSpPr element """
+ """p:grpSpPr element"""
+
+ get_or_add_xfrm: Callable[[], CT_Transform2D]
_tag_seq = (
"a:xfrm",
@@ -273,6 +273,8 @@ class CT_GroupShapeProperties(BaseOxmlElement):
"a:scene3d",
"a:extLst",
)
- xfrm = ZeroOrOne("a:xfrm", successors=_tag_seq[1:])
+ xfrm: CT_Transform2D | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:xfrm", successors=_tag_seq[1:]
+ )
effectLst = ZeroOrOne("a:effectLst", successors=_tag_seq[8:])
del _tag_seq
diff --git a/src/pptx/oxml/shapes/picture.py b/src/pptx/oxml/shapes/picture.py
index 39904385d..bacc97194 100644
--- a/src/pptx/oxml/shapes/picture.py
+++ b/src/pptx/oxml/shapes/picture.py
@@ -1,9 +1,8 @@
-# encoding: utf-8
-
"""lxml custom element classes for picture-related XML elements."""
-from __future__ import division
+from __future__ import annotations
+from typing import TYPE_CHECKING, cast
from xml.sax.saxutils import escape
from pptx.oxml import parse_xml
@@ -11,6 +10,10 @@
from pptx.oxml.shapes.shared import BaseShapeElement
from pptx.oxml.xmlchemy import BaseOxmlElement, OneAndOnlyOne
+if TYPE_CHECKING:
+ from pptx.oxml.shapes.shared import CT_ShapeProperties
+ from pptx.util import Length
+
class CT_Picture(BaseShapeElement):
"""`p:pic` element.
@@ -20,10 +23,10 @@ class CT_Picture(BaseShapeElement):
nvPicPr = OneAndOnlyOne("p:nvPicPr")
blipFill = OneAndOnlyOne("p:blipFill")
- spPr = OneAndOnlyOne("p:spPr")
+ spPr: CT_ShapeProperties = OneAndOnlyOne("p:spPr") # pyright: ignore[reportAssignmentType]
@property
- def blip_rId(self):
+ def blip_rId(self) -> str | None:
"""Value of `p:blipFill/a:blip/@r:embed`.
Returns |None| if not present.
@@ -65,28 +68,38 @@ def new_ph_pic(cls, id_, name, desc, rId):
@classmethod
def new_pic(cls, shape_id, name, desc, rId, x, y, cx, cy):
"""Return new `` element tree configured with supplied parameters."""
- return parse_xml(
- cls._pic_tmpl() % (shape_id, name, escape(desc), rId, x, y, cx, cy)
- )
+ return parse_xml(cls._pic_tmpl() % (shape_id, name, escape(desc), rId, x, y, cx, cy))
@classmethod
def new_video_pic(
- cls, shape_id, shape_name, video_rId, media_rId, poster_frame_rId, x, y, cx, cy
- ):
+ cls,
+ shape_id: int,
+ shape_name: str,
+ video_rId: str,
+ media_rId: str,
+ poster_frame_rId: str,
+ x: Length,
+ y: Length,
+ cx: Length,
+ cy: Length,
+ ) -> CT_Picture:
"""Return a new `p:pic` populated with the specified video."""
- return parse_xml(
- cls._pic_video_tmpl()
- % (
- shape_id,
- shape_name,
- video_rId,
- media_rId,
- poster_frame_rId,
- x,
- y,
- cx,
- cy,
- )
+ return cast(
+ CT_Picture,
+ parse_xml(
+ cls._pic_video_tmpl()
+ % (
+ shape_id,
+ shape_name,
+ video_rId,
+ media_rId,
+ poster_frame_rId,
+ x,
+ y,
+ cx,
+ cy,
+ )
+ ),
)
@property
diff --git a/src/pptx/oxml/shapes/shared.py b/src/pptx/oxml/shapes/shared.py
index 74eb562d3..d9f945697 100644
--- a/src/pptx/oxml/shapes/shared.py
+++ b/src/pptx/oxml/shapes/shared.py
@@ -1,8 +1,8 @@
-# encoding: utf-8
-
"""Common shape-related oxml objects."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Callable
from pptx.dml.fill import CT_GradientFillProperties
from pptx.enum.shapes import PP_PLACEHOLDER
@@ -30,15 +30,19 @@
)
from pptx.util import Emu
+if TYPE_CHECKING:
+ from pptx.oxml.action import CT_Hyperlink
+ from pptx.oxml.shapes.autoshape import CT_CustomGeometry2D, CT_PresetGeometry2D
+ from pptx.util import Length
+
class BaseShapeElement(BaseOxmlElement):
- """
- Provides common behavior for shape element classes like CT_Shape,
- CT_Picture, etc.
- """
+ """Provides common behavior for shape element classes like CT_Shape, CT_Picture, etc."""
+
+ spPr: CT_ShapeProperties
@property
- def cx(self):
+ def cx(self) -> Length:
return self._get_xfrm_attr("cx")
@cx.setter
@@ -46,7 +50,7 @@ def cx(self, value):
self._set_xfrm_attr("cx", value)
@property
- def cy(self):
+ def cy(self) -> Length:
return self._get_xfrm_attr("cy")
@cy.setter
@@ -70,36 +74,34 @@ def flipV(self, value):
self._set_xfrm_attr("flipV", value)
def get_or_add_xfrm(self):
- """
- Return the ```` grandchild element, newly-added if not
- present. This version works for ````, ````, and
- ```` elements, others will need to override.
+ """Return the `a:xfrm` grandchild element, newly-added if not present.
+
+ This version works for `p:sp`, `p:cxnSp`, and `p:pic` elements, others will need to
+ override.
"""
return self.spPr.get_or_add_xfrm()
@property
def has_ph_elm(self):
"""
- True if this shape element has a ```` descendant, indicating it
+ True if this shape element has a `p:ph` descendant, indicating it
is a placeholder shape. False otherwise.
"""
return self.ph is not None
@property
- def ph(self):
- """
- The ```` descendant element if there is one, None otherwise.
- """
+ def ph(self) -> CT_Placeholder | None:
+ """The `p:ph` descendant element if there is one, None otherwise."""
ph_elms = self.xpath("./*[1]/p:nvPr/p:ph")
if len(ph_elms) == 0:
return None
return ph_elms[0]
@property
- def ph_idx(self):
- """
- Integer value of placeholder idx attribute. Raises |ValueError| if
- shape is not a placeholder.
+ def ph_idx(self) -> int:
+ """Integer value of placeholder idx attribute.
+
+ Raises |ValueError| if shape is not a placeholder.
"""
ph = self.ph
if ph is None:
@@ -107,10 +109,10 @@ def ph_idx(self):
return ph.idx
@property
- def ph_orient(self):
- """
- Placeholder orientation, e.g. 'vert'. Raises |ValueError| if shape is
- not a placeholder.
+ def ph_orient(self) -> str:
+ """Placeholder orientation, e.g. 'vert'.
+
+ Raises |ValueError| if shape is not a placeholder.
"""
ph = self.ph
if ph is None:
@@ -118,10 +120,10 @@ def ph_orient(self):
return ph.orient
@property
- def ph_sz(self):
- """
- Placeholder size, e.g. ST_PlaceholderSize.HALF, None if shape has no
- ```` descendant.
+ def ph_sz(self) -> str:
+ """Placeholder size, e.g. ST_PlaceholderSize.HALF.
+
+ Raises `ValueError` if shape is not a placeholder.
"""
ph = self.ph
if ph is None:
@@ -130,9 +132,9 @@ def ph_sz(self):
@property
def ph_type(self):
- """
- Placeholder type, e.g. ST_PlaceholderType.TITLE ('title'), none if
- shape has no ```` descendant.
+ """Placeholder type, e.g. ST_PlaceholderType.TITLE ('title').
+
+ Raises `ValueError` if shape is not a placeholder.
"""
ph = self.ph
if ph is None:
@@ -140,17 +142,15 @@ def ph_type(self):
return ph.type
@property
- def rot(self):
- """
- Float representing degrees this shape is rotated clockwise.
- """
+ def rot(self) -> float:
+ """Float representing degrees this shape is rotated clockwise."""
xfrm = self.xfrm
- if xfrm is None:
+ if xfrm is None or xfrm.rot is None:
return 0.0
return xfrm.rot
@rot.setter
- def rot(self, value):
+ def rot(self, value: float):
self.get_or_add_xfrm().rot = value
@property
@@ -169,13 +169,11 @@ def shape_name(self):
@property
def txBody(self):
- """
- Child ```` element, None if not present
- """
+ """Child `p:txBody` element, None if not present."""
return self.find(qn("p:txBody"))
@property
- def x(self):
+ def x(self) -> Length:
return self._get_xfrm_attr("x")
@x.setter
@@ -184,15 +182,15 @@ def x(self, value):
@property
def xfrm(self):
- """
- The ```` grandchild element or |None| if not found. This
- version works for ````, ````, and ````
- elements, others will need to override.
+ """The `a:xfrm` grandchild element or |None| if not found.
+
+ This version works for `p:sp`, `p:cxnSp`, and `p:pic` elements, others will need to
+ override.
"""
return self.spPr.xfrm
@property
- def y(self):
+ def y(self) -> Length:
return self._get_xfrm_attr("y")
@y.setter
@@ -203,12 +201,12 @@ def y(self, value):
def _nvXxPr(self):
"""
Required non-visual shape properties element for this shape. Actual
- name depends on the shape type, e.g. ```` for picture
+ name depends on the shape type, e.g. `p:nvPicPr` for picture
shape.
"""
return self.xpath("./*[1]")[0]
- def _get_xfrm_attr(self, name):
+ def _get_xfrm_attr(self, name: str) -> Length | None:
xfrm = self.xfrm
if xfrm is None:
return None
@@ -220,9 +218,9 @@ def _set_xfrm_attr(self, name, value):
class CT_ApplicationNonVisualDrawingProps(BaseOxmlElement):
- """
- ```` element
- """
+ """`p:nvPr` element."""
+
+ get_or_add_ph: Callable[[], CT_Placeholder]
ph = ZeroOrOne(
"p:ph",
@@ -295,27 +293,34 @@ def prstDash_val(self, val):
class CT_NonVisualDrawingProps(BaseOxmlElement):
- """
- ```` custom element class.
- """
+ """`p:cNvPr` custom element class."""
+
+ get_or_add_hlinkClick: Callable[[], CT_Hyperlink]
+ get_or_add_hlinkHover: Callable[[], CT_Hyperlink]
_tag_seq = ("a:hlinkClick", "a:hlinkHover", "a:extLst")
- hlinkClick = ZeroOrOne("a:hlinkClick", successors=_tag_seq[1:])
- hlinkHover = ZeroOrOne("a:hlinkHover", successors=_tag_seq[2:])
+ hlinkClick: CT_Hyperlink | None = ZeroOrOne("a:hlinkClick", successors=_tag_seq[1:])
+ hlinkHover: CT_Hyperlink | None = ZeroOrOne("a:hlinkHover", successors=_tag_seq[2:])
id = RequiredAttribute("id", ST_DrawingElementId)
name = RequiredAttribute("name", XsdString)
del _tag_seq
class CT_Placeholder(BaseOxmlElement):
- """
- ```` custom element class.
- """
+ """`p:ph` custom element class."""
- type = OptionalAttribute("type", PP_PLACEHOLDER, default=PP_PLACEHOLDER.OBJECT)
- orient = OptionalAttribute("orient", ST_Direction, default=ST_Direction.HORZ)
- sz = OptionalAttribute("sz", ST_PlaceholderSize, default=ST_PlaceholderSize.FULL)
- idx = OptionalAttribute("idx", XsdUnsignedInt, default=0)
+ type: PP_PLACEHOLDER = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "type", PP_PLACEHOLDER, default=PP_PLACEHOLDER.OBJECT
+ )
+ orient: str = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "orient", ST_Direction, default=ST_Direction.HORZ
+ )
+ sz: str = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "sz", ST_PlaceholderSize, default=ST_PlaceholderSize.FULL
+ )
+ idx: int = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "idx", XsdUnsignedInt, default=0
+ )
class CT_Point2D(BaseOxmlElement):
@@ -323,8 +328,8 @@ class CT_Point2D(BaseOxmlElement):
Custom element class for element.
"""
- x = RequiredAttribute("x", ST_Coordinate)
- y = RequiredAttribute("y", ST_Coordinate)
+ x: Length = RequiredAttribute("x", ST_Coordinate) # pyright: ignore[reportAssignmentType]
+ y: Length = RequiredAttribute("y", ST_Coordinate) # pyright: ignore[reportAssignmentType]
class CT_PositiveSize2D(BaseOxmlElement):
@@ -339,10 +344,14 @@ class CT_PositiveSize2D(BaseOxmlElement):
class CT_ShapeProperties(BaseOxmlElement):
"""Custom element class for `p:spPr` element.
- Shared by `p:sp`, `p:cxnSp`, and `p:pic` elements as well as a few more
- obscure ones.
+ Shared by `p:sp`, `p:cxnSp`, and `p:pic` elements as well as a few more obscure ones.
"""
+ get_or_add_xfrm: Callable[[], CT_Transform2D]
+ get_or_add_ln: Callable[[], CT_LineProperties]
+ _add_prstGeom: Callable[[], CT_PresetGeometry2D]
+ _remove_custGeom: Callable[[], None]
+
_tag_seq = (
"a:xfrm",
"a:custGeom",
@@ -360,9 +369,15 @@ class CT_ShapeProperties(BaseOxmlElement):
"a:sp3d",
"a:extLst",
)
- xfrm = ZeroOrOne("a:xfrm", successors=_tag_seq[1:])
- custGeom = ZeroOrOne("a:custGeom", successors=_tag_seq[2:])
- prstGeom = ZeroOrOne("a:prstGeom", successors=_tag_seq[3:])
+ xfrm: CT_Transform2D | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:xfrm", successors=_tag_seq[1:]
+ )
+ custGeom: CT_CustomGeometry2D | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:custGeom", successors=_tag_seq[2:]
+ )
+ prstGeom: CT_PresetGeometry2D | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:prstGeom", successors=_tag_seq[3:]
+ )
eg_fillProperties = ZeroOrOneChoice(
(
Choice("a:noFill"),
@@ -374,7 +389,9 @@ class CT_ShapeProperties(BaseOxmlElement):
),
successors=_tag_seq[9:],
)
- ln = ZeroOrOne("a:ln", successors=_tag_seq[10:])
+ ln: CT_LineProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:ln", successors=_tag_seq[10:]
+ )
effectLst = ZeroOrOne("a:effectLst", successors=_tag_seq[11:])
del _tag_seq
@@ -399,11 +416,10 @@ def cy(self):
return Emu(cy_str_lst[0])
@property
- def x(self):
- """
- The offset of the left edge of the shape from the left edge of the
- slide, as an instance of Emu. Corresponds to the value of the
- `./xfrm/off/@x` attribute. None if not present.
+ def x(self) -> Length | None:
+ """Distance between the left edge of the slide and left edge of the shape.
+
+ 0 if not present.
"""
x_str_lst = self.xpath("./a:xfrm/a:off/@x")
if not x_str_lst:
@@ -433,12 +449,16 @@ class CT_Transform2D(BaseOxmlElement):
"""
_tag_seq = ("a:off", "a:ext", "a:chOff", "a:chExt")
- off = ZeroOrOne("a:off", successors=_tag_seq[1:])
+ off: CT_Point2D | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:off", successors=_tag_seq[1:]
+ )
ext = ZeroOrOne("a:ext", successors=_tag_seq[2:])
chOff = ZeroOrOne("a:chOff", successors=_tag_seq[3:])
chExt = ZeroOrOne("a:chExt", successors=_tag_seq[4:])
del _tag_seq
- rot = OptionalAttribute("rot", ST_Angle, default=0.0)
+ rot: float | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "rot", ST_Angle, default=0.0
+ )
flipH = OptionalAttribute("flipH", XsdBoolean, default=False)
flipV = OptionalAttribute("flipV", XsdBoolean, default=False)
diff --git a/src/pptx/oxml/simpletypes.py b/src/pptx/oxml/simpletypes.py
index 7c3ed34e5..6ceb06f7c 100644
--- a/src/pptx/oxml/simpletypes.py
+++ b/src/pptx/oxml/simpletypes.py
@@ -1,37 +1,35 @@
-# encoding: utf-8
-
"""Simple-type classes.
-A "simple-type" is a scalar type, generally serving as an XML attribute. This is in
-contrast to a "complex-type" which would specify an XML element.
+A "simple-type" is a scalar type, generally serving as an XML attribute. This is in contrast to a
+"complex-type" which would specify an XML element.
-These objects providing validation and format translation for values stored in XML
-element attributes. Naming generally corresponds to the simple type in the associated
-XML schema.
+These objects providing validation and format translation for values stored in XML element
+attributes. Naming generally corresponds to the simple type in the associated XML schema.
"""
+from __future__ import annotations
+
import numbers
+from typing import Any
from pptx.exc import InvalidXmlError
from pptx.util import Centipoints, Emu
-class BaseSimpleType(object):
+class BaseSimpleType:
@classmethod
- def from_xml(cls, str_value):
- return cls.convert_from_xml(str_value)
+ def from_xml(cls, xml_value: str) -> Any:
+ return cls.convert_from_xml(xml_value)
@classmethod
- def to_xml(cls, value):
+ def to_xml(cls, value: Any) -> str:
cls.validate(value)
str_value = cls.convert_to_xml(value)
return str_value
@classmethod
- def validate_float(cls, value):
- """
- Note that int values are accepted.
- """
+ def validate_float(cls, value: Any):
+ """Note that int values are accepted."""
if not isinstance(value, (int, float)):
raise TypeError("value must be a number, got %s" % type(value))
@@ -151,8 +149,7 @@ def convert_to_xml(cls, value):
def validate(cls, value):
if value not in (True, False):
raise TypeError(
- "only True or False (and possibly None) may be assigned, got"
- " '%s'" % value
+ "only True or False (and possibly None) may be assigned, got" " '%s'" % value
)
@@ -231,7 +228,7 @@ class ST_Angle(XsdInt):
THREE_SIXTY = 360 * DEGREE_INCREMENTS
@classmethod
- def convert_from_xml(cls, str_value):
+ def convert_from_xml(cls, str_value: str) -> float:
rot = int(str_value) % cls.THREE_SIXTY
return float(rot) / cls.DEGREE_INCREMENTS
@@ -349,9 +346,7 @@ def validate(cls, value):
class ST_Direction(XsdTokenEnumeration):
- """
- Valid values for attribute
- """
+ """Valid values for `` attribute."""
HORZ = "horz"
VERT = "vert"
@@ -419,17 +414,13 @@ def validate(cls, value):
# must be 6 chars long----------
if len(str_value) != 6:
- raise ValueError(
- "RGB string must be six characters long, got '%s'" % str_value
- )
+ raise ValueError("RGB string must be six characters long, got '%s'" % str_value)
# must parse as hex int --------
try:
int(str_value, 16)
except ValueError:
- raise ValueError(
- "RGB string must be valid hex string, got '%s'" % str_value
- )
+ raise ValueError("RGB string must be valid hex string, got '%s'" % str_value)
class ST_LayoutMode(XsdStringEnumeration):
@@ -471,8 +462,7 @@ def validate(cls, value):
super(ST_LineWidth, cls).validate(value)
if value < 0 or value > 20116800:
raise ValueError(
- "value must be in range 0-20116800 inclusive (0-1584 points)"
- ", got %d" % value
+ "value must be in range 0-20116800 inclusive (0-1584 points)" ", got %d" % value
)
@@ -615,8 +605,7 @@ def validate(cls, value):
cls.validate_int(value)
if value < 914400 or value > 51206400:
raise ValueError(
- "value must be in range(914400, 51206400) (1-56 inches), got"
- " %d" % value
+ "value must be in range(914400, 51206400) (1-56 inches), got" " %d" % value
)
@@ -636,9 +625,7 @@ class ST_TargetMode(XsdString):
def validate(cls, value):
cls.validate_string(value)
if value not in ("External", "Internal"):
- raise ValueError(
- "must be one of 'Internal' or 'External', got '%s'" % value
- )
+ raise ValueError("must be one of 'Internal' or 'External', got '%s'" % value)
class ST_TextFontScalePercentOrPercentString(BaseFloatType):
@@ -661,9 +648,7 @@ def convert_to_xml(cls, value):
def validate(cls, value):
BaseFloatType.validate(value)
if value < 1.0 or value > 100.0:
- raise ValueError(
- "value must be in range 1.0..100.0 (percent), got %s" % value
- )
+ raise ValueError("value must be in range 1.0..100.0 (percent), got %s" % value)
class ST_TextFontSize(BaseIntType):
diff --git a/src/pptx/oxml/slide.py b/src/pptx/oxml/slide.py
index 36b868cf8..37a9780f6 100644
--- a/src/pptx/oxml/slide.py
+++ b/src/pptx/oxml/slide.py
@@ -1,8 +1,8 @@
-# encoding: utf-8
-
"""Slide-related custom element classes, including those for masters."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Callable, cast
from pptx.oxml import parse_from_template, parse_xml
from pptx.oxml.dml.fill import CT_GradientFillProperties
@@ -19,39 +19,39 @@
ZeroOrOneChoice,
)
+if TYPE_CHECKING:
+ from pptx.oxml.shapes.groupshape import CT_GroupShape
+
class _BaseSlideElement(BaseOxmlElement):
- """
- Base class for the six slide types, providing common methods.
- """
+ """Base class for the six slide types, providing common methods."""
+
+ cSld: CT_CommonSlideData
@property
- def spTree(self):
- """
- Return required `p:cSld/p:spTree` grandchild.
- """
+ def spTree(self) -> CT_GroupShape:
+ """Return required `p:cSld/p:spTree` grandchild."""
return self.cSld.spTree
class CT_Background(BaseOxmlElement):
"""`p:bg` element."""
+ _insert_bgPr: Callable[[CT_BackgroundProperties], None]
+
# ---these two are actually a choice, not a sequence, but simpler for
# ---present purposes this way.
_tag_seq = ("p:bgPr", "p:bgRef")
- bgPr = ZeroOrOne("p:bgPr", successors=())
+ bgPr: CT_BackgroundProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "p:bgPr", successors=()
+ )
bgRef = ZeroOrOne("p:bgRef", successors=())
del _tag_seq
def add_noFill_bgPr(self):
"""Return a new `p:bgPr` element with noFill properties."""
- xml = (
- "\n"
- " \n"
- " \n"
- "" % nsdecls("a", "p")
- )
- bgPr = parse_xml(xml)
+ xml = "\n" " \n" " \n" "" % nsdecls("a", "p")
+ bgPr = cast(CT_BackgroundProperties, parse_xml(xml))
self._insert_bgPr(bgPr)
return bgPr
@@ -91,24 +91,31 @@ def _new_gradFill(self):
class CT_CommonSlideData(BaseOxmlElement):
"""`p:cSld` element."""
+ _remove_bg: Callable[[], None]
+ get_or_add_bg: Callable[[], CT_Background]
+
_tag_seq = ("p:bg", "p:spTree", "p:custDataLst", "p:controls", "p:extLst")
- bg = ZeroOrOne("p:bg", successors=_tag_seq[1:])
- spTree = OneAndOnlyOne("p:spTree")
+ bg: CT_Background | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "p:bg", successors=_tag_seq[1:]
+ )
+ spTree: CT_GroupShape = OneAndOnlyOne("p:spTree") # pyright: ignore[reportAssignmentType]
del _tag_seq
- name = OptionalAttribute("name", XsdString, default="")
+ name: str = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "name", XsdString, default=""
+ )
- def get_or_add_bgPr(self):
+ def get_or_add_bgPr(self) -> CT_BackgroundProperties:
"""Return `p:bg/p:bgPr` grandchild.
- If no such grandchild is present, any existing `p:bg` child is first
- removed and a new default `p:bg` with noFill settings is added.
+ If no such grandchild is present, any existing `p:bg` child is first removed and a new
+ default `p:bg` with noFill settings is added.
"""
bg = self.bg
if bg is None or bg.bgPr is None:
- self._change_to_noFill_bg()
- return self.bg.bgPr
+ bg = self._change_to_noFill_bg()
+ return cast(CT_BackgroundProperties, bg.bgPr)
- def _change_to_noFill_bg(self):
+ def _change_to_noFill_bg(self) -> CT_Background:
"""Establish a `p:bg` child with no-fill settings.
Any existing `p:bg` child is first removed.
@@ -120,55 +127,48 @@ def _change_to_noFill_bg(self):
class CT_NotesMaster(_BaseSlideElement):
- """
- ```` element, root of a notes master part
- """
+ """`p:notesMaster` element, root of a notes master part."""
_tag_seq = ("p:cSld", "p:clrMap", "p:hf", "p:notesStyle", "p:extLst")
- cSld = OneAndOnlyOne("p:cSld")
+ cSld: CT_CommonSlideData = OneAndOnlyOne("p:cSld") # pyright: ignore[reportAssignmentType]
del _tag_seq
@classmethod
- def new_default(cls):
- """
- Return a new ```` element based on the built-in
- default template.
- """
- return parse_from_template("notesMaster")
+ def new_default(cls) -> CT_NotesMaster:
+ """Return a new `p:notesMaster` element based on the built-in default template."""
+ return cast(CT_NotesMaster, parse_from_template("notesMaster"))
class CT_NotesSlide(_BaseSlideElement):
- """
- ```` element, root of a notes slide part
- """
+ """`p:notes` element, root of a notes slide part."""
_tag_seq = ("p:cSld", "p:clrMapOvr", "p:extLst")
- cSld = OneAndOnlyOne("p:cSld")
+ cSld: CT_CommonSlideData = OneAndOnlyOne("p:cSld") # pyright: ignore[reportAssignmentType]
del _tag_seq
@classmethod
- def new(cls):
- """
- Return a new ```` element based on the default template.
- Note that the template does not include placeholders, which must be
- subsequently cloned from the notes master.
+ def new(cls) -> CT_NotesSlide:
+ """Return a new ```` element based on the default template.
+
+ Note that the template does not include placeholders, which must be subsequently cloned
+ from the notes master.
"""
- return parse_from_template("notes")
+ return cast(CT_NotesSlide, parse_from_template("notes"))
class CT_Slide(_BaseSlideElement):
"""`p:sld` element, root element of a slide part (XML document)."""
_tag_seq = ("p:cSld", "p:clrMapOvr", "p:transition", "p:timing", "p:extLst")
- cSld = OneAndOnlyOne("p:cSld")
+ cSld: CT_CommonSlideData = OneAndOnlyOne("p:cSld") # pyright: ignore[reportAssignmentType]
clrMapOvr = ZeroOrOne("p:clrMapOvr", successors=_tag_seq[2:])
timing = ZeroOrOne("p:timing", successors=_tag_seq[4:])
del _tag_seq
@classmethod
- def new(cls):
+ def new(cls) -> CT_Slide:
"""Return new `p:sld` element configured as base slide shape."""
- return parse_xml(cls._sld_xml())
+ return cast(CT_Slide, parse_xml(cls._sld_xml()))
@property
def bg(self):
@@ -252,37 +252,37 @@ def _sld_xml():
class CT_SlideLayout(_BaseSlideElement):
- """
- ```` element, root of a slide layout part
- """
+ """`p:sldLayout` element, root of a slide layout part."""
_tag_seq = ("p:cSld", "p:clrMapOvr", "p:transition", "p:timing", "p:hf", "p:extLst")
- cSld = OneAndOnlyOne("p:cSld")
+ cSld: CT_CommonSlideData = OneAndOnlyOne("p:cSld") # pyright: ignore[reportAssignmentType]
del _tag_seq
class CT_SlideLayoutIdList(BaseOxmlElement):
+ """`p:sldLayoutIdLst` element, child of `p:sldMaster`.
+
+ Contains references to the slide layouts that inherit from the slide master.
"""
- ```` element, child of ```` containing
- references to the slide layouts that inherit from the slide master.
- """
+
+ sldLayoutId_lst: list[CT_SlideLayoutIdListEntry]
sldLayoutId = ZeroOrMore("p:sldLayoutId")
class CT_SlideLayoutIdListEntry(BaseOxmlElement):
- """
- ```` element, child of ```` containing
- a reference to a slide layout.
+ """`p:sldLayoutId` element, child of `p:sldLayoutIdLst`.
+
+ Contains a reference to a slide layout.
"""
- rId = RequiredAttribute("r:id", XsdString)
+ rId: str = RequiredAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
class CT_SlideMaster(_BaseSlideElement):
- """
- ```` element, root of a slide master part
- """
+ """`p:sldMaster` element, root of a slide master part."""
+
+ get_or_add_sldLayoutIdLst: Callable[[], CT_SlideLayoutIdList]
_tag_seq = (
"p:cSld",
@@ -294,8 +294,10 @@ class CT_SlideMaster(_BaseSlideElement):
"p:txStyles",
"p:extLst",
)
- cSld = OneAndOnlyOne("p:cSld")
- sldLayoutIdLst = ZeroOrOne("p:sldLayoutIdLst", successors=_tag_seq[3:])
+ cSld: CT_CommonSlideData = OneAndOnlyOne("p:cSld") # pyright: ignore[reportAssignmentType]
+ sldLayoutIdLst: CT_SlideLayoutIdList = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "p:sldLayoutIdLst", successors=_tag_seq[3:]
+ )
del _tag_seq
diff --git a/src/pptx/oxml/table.py b/src/pptx/oxml/table.py
index 5b0bd5b6d..cd3e9ebc3 100644
--- a/src/pptx/oxml/table.py
+++ b/src/pptx/oxml/table.py
@@ -1,8 +1,8 @@
-# encoding: utf-8
-
"""Custom element classes for table-related XML elements"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Callable, Iterator, cast
from pptx.enum.text import MSO_VERTICAL_ANCHOR
from pptx.oxml import parse_xml
@@ -22,87 +22,95 @@
)
from pptx.util import Emu, lazyproperty
+if TYPE_CHECKING:
+ from pptx.util import Length
+
class CT_Table(BaseOxmlElement):
"""`a:tbl` custom element class"""
+ get_or_add_tblPr: Callable[[], CT_TableProperties]
+ tr_lst: list[CT_TableRow]
+ _add_tr: Callable[..., CT_TableRow]
+
_tag_seq = ("a:tblPr", "a:tblGrid", "a:tr")
- tblPr = ZeroOrOne("a:tblPr", successors=_tag_seq[1:])
- tblGrid = OneAndOnlyOne("a:tblGrid")
+ tblPr: CT_TableProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:tblPr", successors=_tag_seq[1:]
+ )
+ tblGrid: CT_TableGrid = OneAndOnlyOne("a:tblGrid") # pyright: ignore[reportAssignmentType]
tr = ZeroOrMore("a:tr", successors=_tag_seq[3:])
del _tag_seq
- def add_tr(self, height):
- """
- Return a reference to a newly created child element having its
- ``h`` attribute set to *height*.
- """
+ def add_tr(self, height: Length) -> CT_TableRow:
+ """Return a newly created `a:tr` child element having its `h` attribute set to `height`."""
return self._add_tr(h=height)
@property
- def bandCol(self):
+ def bandCol(self) -> bool:
return self._get_boolean_property("bandCol")
@bandCol.setter
- def bandCol(self, value):
+ def bandCol(self, value: bool):
self._set_boolean_property("bandCol", value)
@property
- def bandRow(self):
+ def bandRow(self) -> bool:
return self._get_boolean_property("bandRow")
@bandRow.setter
- def bandRow(self, value):
+ def bandRow(self, value: bool):
self._set_boolean_property("bandRow", value)
@property
- def firstCol(self):
+ def firstCol(self) -> bool:
return self._get_boolean_property("firstCol")
@firstCol.setter
- def firstCol(self, value):
+ def firstCol(self, value: bool):
self._set_boolean_property("firstCol", value)
@property
- def firstRow(self):
+ def firstRow(self) -> bool:
return self._get_boolean_property("firstRow")
@firstRow.setter
- def firstRow(self, value):
+ def firstRow(self, value: bool):
self._set_boolean_property("firstRow", value)
- def iter_tcs(self):
+ def iter_tcs(self) -> Iterator[CT_TableCell]:
"""Generate each `a:tc` element in this tbl.
- tc elements are generated left-to-right, top-to-bottom.
+ `a:tc` elements are generated left-to-right, top-to-bottom.
"""
return (tc for tr in self.tr_lst for tc in tr.tc_lst)
@property
- def lastCol(self):
+ def lastCol(self) -> bool:
return self._get_boolean_property("lastCol")
@lastCol.setter
- def lastCol(self, value):
+ def lastCol(self, value: bool):
self._set_boolean_property("lastCol", value)
@property
- def lastRow(self):
+ def lastRow(self) -> bool:
return self._get_boolean_property("lastRow")
@lastRow.setter
- def lastRow(self, value):
+ def lastRow(self, value: bool):
self._set_boolean_property("lastRow", value)
@classmethod
- def new_tbl(cls, rows, cols, width, height, tableStyleId=None):
- """Return a new ```` element tree."""
+ def new_tbl(
+ cls, rows: int, cols: int, width: int, height: int, tableStyleId: str | None = None
+ ) -> CT_Table:
+ """Return a new `p:tbl` element tree."""
# working hypothesis is this is the default table style GUID
if tableStyleId is None:
tableStyleId = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"
xml = cls._tbl_tmpl() % (tableStyleId)
- tbl = parse_xml(xml)
+ tbl = cast(CT_Table, parse_xml(xml))
# add specified number of rows and columns
rowheight = height // rows
@@ -112,27 +120,27 @@ def new_tbl(cls, rows, cols, width, height, tableStyleId=None):
# adjust width of last col to absorb any div error
if col == cols - 1:
colwidth = width - ((cols - 1) * colwidth)
- tbl.tblGrid.add_gridCol(width=colwidth)
+ tbl.tblGrid.add_gridCol(width=Emu(colwidth))
for row in range(rows):
# adjust height of last row to absorb any div error
if row == rows - 1:
rowheight = height - ((rows - 1) * rowheight)
- tr = tbl.add_tr(height=rowheight)
+ tr = tbl.add_tr(height=Emu(rowheight))
for col in range(cols):
tr.add_tc()
return tbl
- def tc(self, row_idx, col_idx):
- """Return `a:tc` element at *row_idx*, *col_idx*."""
+ def tc(self, row_idx: int, col_idx: int) -> CT_TableCell:
+ """Return `a:tc` element at `row_idx`, `col_idx`."""
return self.tr_lst[row_idx].tc_lst[col_idx]
- def _get_boolean_property(self, propname):
- """
- Generalized getter for the boolean properties on the ````
- child element. Defaults to False if *propname* attribute is missing
- or ```` element itself is not present.
+ def _get_boolean_property(self, propname: str) -> bool:
+ """Generalized getter for the boolean properties on the `a:tblPr` child element.
+
+ Defaults to False if `propname` attribute is missing or `a:tblPr` element itself is not
+ present.
"""
tblPr = self.tblPr
if tblPr is None:
@@ -140,19 +148,16 @@ def _get_boolean_property(self, propname):
propval = getattr(tblPr, propname)
return {True: True, False: False, None: False}[propval]
- def _set_boolean_property(self, propname, value):
- """
- Generalized setter for boolean properties on the ```` child
- element, setting *propname* attribute appropriately based on *value*.
- If *value* is True, the attribute is set to "1"; a tblPr child
- element is added if necessary. If *value* is False, the *propname*
- attribute is removed if present, allowing its default value of False
- to be its effective value.
+ def _set_boolean_property(self, propname: str, value: bool) -> None:
+ """Generalized setter for boolean properties on the `a:tblPr` child element.
+
+ Sets `propname` attribute appropriately based on `value`. If `value` is True, the
+ attribute is set to "1"; a tblPr child element is added if necessary. If `value` is False,
+ the `propname` attribute is removed if present, allowing its default value of False to be
+ its effective value.
"""
if value not in (True, False):
- raise ValueError(
- "assigned value must be either True or False, got %s" % value
- )
+ raise ValueError("assigned value must be either True or False, got %s" % value)
tblPr = self.get_or_add_tblPr()
setattr(tblPr, propname, value)
@@ -171,43 +176,52 @@ def _tbl_tmpl(cls):
class CT_TableCell(BaseOxmlElement):
"""`a:tc` custom element class"""
+ get_or_add_tcPr: Callable[[], CT_TableCellProperties]
+ get_or_add_txBody: Callable[[], CT_TextBody]
+
_tag_seq = ("a:txBody", "a:tcPr", "a:extLst")
- txBody = ZeroOrOne("a:txBody", successors=_tag_seq[1:])
- tcPr = ZeroOrOne("a:tcPr", successors=_tag_seq[2:])
+ txBody: CT_TextBody | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:txBody", successors=_tag_seq[1:]
+ )
+ tcPr: CT_TableCellProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:tcPr", successors=_tag_seq[2:]
+ )
del _tag_seq
- gridSpan = OptionalAttribute("gridSpan", XsdInt, default=1)
- rowSpan = OptionalAttribute("rowSpan", XsdInt, default=1)
- hMerge = OptionalAttribute("hMerge", XsdBoolean, default=False)
- vMerge = OptionalAttribute("vMerge", XsdBoolean, default=False)
+ gridSpan: int = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "gridSpan", XsdInt, default=1
+ )
+ rowSpan: int = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "rowSpan", XsdInt, default=1
+ )
+ hMerge: bool = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "hMerge", XsdBoolean, default=False
+ )
+ vMerge: bool = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "vMerge", XsdBoolean, default=False
+ )
@property
- def anchor(self):
- """
- String held in ``anchor`` attribute of ```` child element of
- this ```` element.
- """
+ def anchor(self) -> MSO_VERTICAL_ANCHOR | None:
+ """String held in `anchor` attribute of `a:tcPr` child element of this `a:tc` element."""
if self.tcPr is None:
return None
return self.tcPr.anchor
@anchor.setter
- def anchor(self, anchor_enum_idx):
- """
- Set value of anchor attribute on ```` child element
- """
+ def anchor(self, anchor_enum_idx: MSO_VERTICAL_ANCHOR | None):
+ """Set value of anchor attribute on `a:tcPr` child element."""
if anchor_enum_idx is None and self.tcPr is None:
return
tcPr = self.get_or_add_tcPr()
tcPr.anchor = anchor_enum_idx
- def append_ps_from(self, spanned_tc):
- """Append `a:p` elements taken from *spanned_tc*.
+ def append_ps_from(self, spanned_tc: CT_TableCell):
+ """Append `a:p` elements taken from `spanned_tc`.
- Any non-empty paragraph elements in *spanned_tc* are removed and
- appended to the text-frame of this cell. If *spanned_tc* is left with
- no content after this process, a single empty `a:p` element is added
- to ensure the cell is compliant with the spec.
+ Any non-empty paragraph elements in `spanned_tc` are removed and appended to the
+ text-frame of this cell. If `spanned_tc` is left with no content after this process, a
+ single empty `a:p` element is added to ensure the cell is compliant with the spec.
"""
source_txBody = spanned_tc.get_or_add_txBody()
target_txBody = self.get_or_add_txBody()
@@ -228,94 +242,96 @@ def append_ps_from(self, spanned_tc):
target_txBody.unclear_content()
@property
- def col_idx(self):
+ def col_idx(self) -> int:
"""Offset of this cell's column in its table."""
# ---tc elements come before any others in `a:tr` element---
- return self.getparent().index(self)
+ return cast(CT_TableRow, self.getparent()).index(self)
@property
- def is_merge_origin(self):
+ def is_merge_origin(self) -> bool:
"""True if cell is top-left in merged cell range."""
if self.gridSpan > 1 and not self.vMerge:
return True
- if self.rowSpan > 1 and not self.hMerge:
- return True
- return False
+ return self.rowSpan > 1 and not self.hMerge
@property
- def is_spanned(self):
+ def is_spanned(self) -> bool:
"""True if cell is in merged cell range but not merge origin cell."""
return self.hMerge or self.vMerge
@property
- def marT(self):
- """
- Read/write integer top margin value represented in ``marT`` attribute
- of the ```` child element of this ```` element. If the
- attribute is not present, the default value ``45720`` (0.05 inches)
- is returned for top and bottom; ``91440`` (0.10 inches) is the
- default for left and right. Assigning |None| to any ``marX``
- property clears that attribute from the element, effectively setting
- it to the default value.
+ def marT(self) -> Length:
+ """Top margin for this cell.
+
+ This value is stored in the `marT` attribute of the `a:tcPr` child element of this `a:tc`.
+
+ Read/write. If the attribute is not present, the default value `45720` (0.05 inches) is
+ returned for top and bottom; `91440` (0.10 inches) is the default for left and right.
+ Assigning |None| to any `marX` property clears that attribute from the element,
+ effectively setting it to the default value.
"""
- return self._get_marX("marT", 45720)
+ return self._get_marX("marT", Emu(45720))
@marT.setter
- def marT(self, value):
+ def marT(self, value: Length | None):
self._set_marX("marT", value)
@property
- def marR(self):
- """
- Right margin value represented in ``marR`` attribute.
- """
- return self._get_marX("marR", 91440)
+ def marR(self) -> Length:
+ """Right margin value represented in `marR` attribute."""
+ return self._get_marX("marR", Emu(91440))
@marR.setter
- def marR(self, value):
+ def marR(self, value: Length | None):
self._set_marX("marR", value)
@property
- def marB(self):
- """
- Bottom margin value represented in ``marB`` attribute.
- """
- return self._get_marX("marB", 45720)
+ def marB(self) -> Length:
+ """Bottom margin value represented in `marB` attribute."""
+ return self._get_marX("marB", Emu(45720))
@marB.setter
- def marB(self, value):
+ def marB(self, value: Length | None):
self._set_marX("marB", value)
@property
- def marL(self):
- """
- Left margin value represented in ``marL`` attribute.
- """
- return self._get_marX("marL", 91440)
+ def marL(self) -> Length:
+ """Left margin value represented in `marL` attribute."""
+ return self._get_marX("marL", Emu(91440))
@marL.setter
- def marL(self, value):
+ def marL(self, value: Length | None):
self._set_marX("marL", value)
@classmethod
- def new(cls):
+ def new(cls) -> CT_TableCell:
"""Return a new `a:tc` element subtree."""
- xml = cls._tc_tmpl()
- tc = parse_xml(xml)
- return tc
+ return cast(
+ CT_TableCell,
+ parse_xml(
+ f"\n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f" \n"
+ f""
+ ),
+ )
@property
- def row_idx(self):
+ def row_idx(self) -> int:
"""Offset of this cell's row in its table."""
- return self.getparent().row_idx
+ return cast(CT_TableRow, self.getparent()).row_idx
@property
- def tbl(self):
+ def tbl(self) -> CT_Table:
"""Table element this cell belongs to."""
- return self.xpath("ancestor::a:tbl")[0]
+ return cast(CT_Table, self.xpath("ancestor::a:tbl")[0])
@property
- def text(self):
+ def text(self) -> str: # pyright: ignore[reportIncompatibleMethodOverride]
"""str text contained in cell"""
# ---note this shadows lxml _Element.text---
txBody = self.txBody
@@ -323,41 +339,26 @@ def text(self):
return ""
return "\n".join([p.text for p in txBody.p_lst])
- def _get_marX(self, attr_name, default):
- """
- Generalized method to get margin values.
- """
+ def _get_marX(self, attr_name: str, default: Length) -> Length:
+ """Generalized method to get margin values."""
if self.tcPr is None:
return Emu(default)
return Emu(int(self.tcPr.get(attr_name, default)))
- def _new_txBody(self):
+ def _new_txBody(self) -> CT_TextBody:
return CT_TextBody.new_a_txBody()
- def _set_marX(self, marX, value):
- """
- Set value of marX attribute on ```` child element. If *marX*
- is |None|, the marX attribute is removed. *marX* is a string, one of
- ``('marL', 'marR', 'marT', 'marB')``.
+ def _set_marX(self, marX: str, value: Length | None) -> None:
+ """Set value of marX attribute on `a:tcPr` child element.
+
+ If `marX` is |None|, the marX attribute is removed. `marX` is a string, one of `('marL',
+ 'marR', 'marT', 'marB')`.
"""
if value is None and self.tcPr is None:
return
tcPr = self.get_or_add_tcPr()
setattr(tcPr, marX, value)
- @classmethod
- def _tc_tmpl(cls):
- return (
- "\n"
- " \n"
- " \n"
- " \n"
- " \n"
- " \n"
- " \n"
- "" % nsdecls("a")
- )
-
class CT_TableCellProperties(BaseOxmlElement):
"""`a:tcPr` custom element class"""
@@ -373,43 +374,47 @@ class CT_TableCellProperties(BaseOxmlElement):
),
successors=("a:headers", "a:extLst"),
)
- anchor = OptionalAttribute("anchor", MSO_VERTICAL_ANCHOR)
- marL = OptionalAttribute("marL", ST_Coordinate32)
- marR = OptionalAttribute("marR", ST_Coordinate32)
- marT = OptionalAttribute("marT", ST_Coordinate32)
- marB = OptionalAttribute("marB", ST_Coordinate32)
+ anchor: MSO_VERTICAL_ANCHOR | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "anchor", MSO_VERTICAL_ANCHOR
+ )
+ marL: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "marL", ST_Coordinate32
+ )
+ marR: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "marR", ST_Coordinate32
+ )
+ marT: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "marT", ST_Coordinate32
+ )
+ marB: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "marB", ST_Coordinate32
+ )
def _new_gradFill(self):
return CT_GradientFillProperties.new_gradFill()
class CT_TableCol(BaseOxmlElement):
- """
- ```` custom element class
- """
+ """`a:gridCol` custom element class."""
- w = RequiredAttribute("w", ST_Coordinate)
+ w: Length = RequiredAttribute("w", ST_Coordinate) # pyright: ignore[reportAssignmentType]
class CT_TableGrid(BaseOxmlElement):
- """
- ```` custom element class
- """
+ """`a:tblGrid` custom element class."""
+
+ gridCol_lst: list[CT_TableCol]
+ _add_gridCol: Callable[..., CT_TableCol]
gridCol = ZeroOrMore("a:gridCol")
- def add_gridCol(self, width):
- """
- Return a reference to a newly created child element
- having its ``w`` attribute set to *width*.
- """
+ def add_gridCol(self, width: Length) -> CT_TableCol:
+ """A newly appended `a:gridCol` child element having its `w` attribute set to `width`."""
return self._add_gridCol(w=width)
class CT_TableProperties(BaseOxmlElement):
- """
- ```` custom element class
- """
+ """`a:tblPr` custom element class."""
bandRow = OptionalAttribute("bandRow", XsdBoolean, default=False)
bandCol = OptionalAttribute("bandCol", XsdBoolean, default=False)
@@ -420,24 +425,22 @@ class CT_TableProperties(BaseOxmlElement):
class CT_TableRow(BaseOxmlElement):
- """
- ```` custom element class
- """
+ """`a:tr` custom element class."""
+
+ tc_lst: list[CT_TableCell]
+ _add_tc: Callable[[], CT_TableCell]
tc = ZeroOrMore("a:tc", successors=("a:extLst",))
- h = RequiredAttribute("h", ST_Coordinate)
+ h: Length = RequiredAttribute("h", ST_Coordinate) # pyright: ignore[reportAssignmentType]
- def add_tc(self):
- """
- Return a reference to a newly added minimal valid ```` child
- element.
- """
+ def add_tc(self) -> CT_TableCell:
+ """A newly added minimal valid `a:tc` child element."""
return self._add_tc()
@property
- def row_idx(self):
+ def row_idx(self) -> int:
"""Offset of this row in its table."""
- return self.getparent().tr_lst.index(self)
+ return cast(CT_Table, self.getparent()).tr_lst.index(self)
def _new_tc(self):
return CT_TableCell.new()
@@ -446,21 +449,19 @@ def _new_tc(self):
class TcRange(object):
"""A 2D block of `a:tc` cell elements in a table.
- This object assumes the structure of the underlying table does not change
- during its lifetime. Structural changes in this context would be
- insertion or removal of rows or columns.
+ This object assumes the structure of the underlying table does not change during its lifetime.
+ Structural changes in this context would be insertion or removal of rows or columns.
- The client is expected to create, use, and then abandon an instance in
- the context of a single user operation that is known to have no
- structural side-effects of this type.
+ The client is expected to create, use, and then abandon an instance in the context of a single
+ user operation that is known to have no structural side-effects of this type.
"""
- def __init__(self, tc, other_tc):
+ def __init__(self, tc: CT_TableCell, other_tc: CT_TableCell):
self._tc = tc
self._other_tc = other_tc
@classmethod
- def from_merge_origin(cls, tc):
+ def from_merge_origin(cls, tc: CT_TableCell):
"""Return instance created from merge-origin tc element."""
other_tc = tc.tbl.tc(
tc.row_idx + tc.rowSpan - 1, # ---other_row_idx
@@ -469,7 +470,7 @@ def from_merge_origin(cls, tc):
return cls(tc, other_tc)
@lazyproperty
- def contains_merged_cell(self):
+ def contains_merged_cell(self) -> bool:
"""True if one or more cells in range are part of a merged cell."""
for tc in self.iter_tcs():
if tc.gridSpan > 1:
@@ -483,7 +484,7 @@ def contains_merged_cell(self):
return False
@lazyproperty
- def dimensions(self):
+ def dimensions(self) -> tuple[int, int]:
"""(row_count, col_count) pair describing size of range."""
_, _, width, height = self._extents
return height, width
@@ -544,16 +545,15 @@ def _bottom(self):
return top + height
@lazyproperty
- def _extents(self):
+ def _extents(self) -> tuple[int, int, int, int]:
"""A (left, top, width, height) tuple describing range extents.
- Note this is normalized to accommodate the various orderings of the
- corner cells provided on construction, which may be in any of four
- configurations such as (top-left, bottom-right),
- (bottom-left, top-right), etc.
+ Note this is normalized to accommodate the various orderings of the corner cells provided
+ on construction, which may be in any of four configurations such as (top-left,
+ bottom-right), (bottom-left, top-right), etc.
"""
- def start_and_size(idx, other_idx):
+ def start_and_size(idx: int, other_idx: int) -> tuple[int, int]:
"""Return beginning and length of range based on two indexes."""
return min(idx, other_idx), abs(idx - other_idx) + 1
@@ -566,23 +566,23 @@ def start_and_size(idx, other_idx):
@lazyproperty
def _left(self):
- """Index of leftmost column in range"""
+ """Index of leftmost column in range."""
left, _, _, _ = self._extents
return left
@lazyproperty
def _right(self):
- """Index of column following the last column in range"""
+ """Index of column following the last column in range."""
left, _, width, _ = self._extents
return left + width
@lazyproperty
def _tbl(self):
- """`a:tbl` element containing this cell range"""
+ """`a:tbl` element containing this cell range."""
return self._tc.tbl
@lazyproperty
def _top(self):
- """Index of topmost row in range"""
+ """Index of topmost row in range."""
_, top, _, _ = self._extents
return top
diff --git a/src/pptx/oxml/text.py b/src/pptx/oxml/text.py
index ced0f8088..0f9ecc152 100644
--- a/src/pptx/oxml/text.py
+++ b/src/pptx/oxml/text.py
@@ -1,12 +1,10 @@
-# encoding: utf-8
-
"""Custom element classes for text-related XML elements"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import re
+from typing import TYPE_CHECKING, Callable, cast
-from pptx.compat import to_unicode
from pptx.enum.lang import MSO_LANGUAGE_ID
from pptx.enum.text import (
MSO_AUTO_SIZE,
@@ -42,27 +40,33 @@
)
from pptx.util import Emu, Length
+if TYPE_CHECKING:
+ from pptx.oxml.action import CT_Hyperlink
+
class CT_RegularTextRun(BaseOxmlElement):
"""`a:r` custom element class"""
- rPr = ZeroOrOne("a:rPr", successors=("a:t",))
- t = OneAndOnlyOne("a:t")
+ get_or_add_rPr: Callable[[], CT_TextCharacterProperties]
+
+ rPr: CT_TextCharacterProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:rPr", successors=("a:t",)
+ )
+ t: BaseOxmlElement = OneAndOnlyOne("a:t") # pyright: ignore[reportAssignmentType]
@property
- def text(self):
- """(unicode) str containing text of (required) `a:t` child"""
+ def text(self) -> str:
+ """All text of (required) `a:t` child."""
text = self.t.text
- # t.text is None when t element is empty, e.g. ''
- return to_unicode(text) if text is not None else ""
+ # -- t.text is None when t element is empty, e.g. '' --
+ return text or ""
@text.setter
- def text(self, str):
- """*str* is unicode value to replace run text."""
- self.t.text = self._escape_ctrl_chars(str)
+ def text(self, value: str): # pyright: ignore[reportIncompatibleMethodOverride]
+ self.t.text = self._escape_ctrl_chars(value)
@staticmethod
- def _escape_ctrl_chars(s):
+ def _escape_ctrl_chars(s: str) -> str:
"""Return str after replacing each control character with a plain-text escape.
For example, a BEL character (x07) would appear as "_x0007_". Horizontal-tab
@@ -78,8 +82,13 @@ class CT_TextBody(BaseOxmlElement):
Also used for `c:txPr` in charts and perhaps other elements.
"""
- bodyPr = OneAndOnlyOne("a:bodyPr")
- p = OneOrMore("a:p")
+ add_p: Callable[[], CT_TextParagraph]
+ p_lst: list[CT_TextParagraph]
+
+ bodyPr: CT_TextBodyProperties = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
+ "a:bodyPr"
+ )
+ p: CT_TextParagraph = OneOrMore("a:p") # pyright: ignore[reportAssignmentType]
def clear_content(self):
"""Remove all `a:p` children, but leave any others.
@@ -90,12 +99,11 @@ def clear_content(self):
self.remove(p)
@property
- def defRPr(self):
- """
- ```` element of required first ``p`` child, added with its
- ancestors if not present. Used when element is a ```` in
- a chart and the ``p`` element is used only to specify formatting, not
- content.
+ def defRPr(self) -> CT_TextCharacterProperties:
+ """`a:defRPr` element of required first `p` child, added with its ancestors if not present.
+
+ Used when element is a ``c:txPr`` in a chart and the `p` element is used only to specify
+ formatting, not content.
"""
p = self.p_lst[0]
pPr = p.get_or_add_pPr()
@@ -103,7 +111,7 @@ def defRPr(self):
return defRPr
@property
- def is_empty(self):
+ def is_empty(self) -> bool:
"""True if only a single empty `a:p` element is present."""
ps = self.p_lst
if len(ps) > 1:
@@ -118,37 +126,32 @@ def is_empty(self):
@classmethod
def new(cls):
- """
- Return a new ```` element tree
- """
+ """Return a new `p:txBody` element tree."""
xml = cls._txBody_tmpl()
txBody = parse_xml(xml)
return txBody
@classmethod
- def new_a_txBody(cls):
- """
- Return a new ```` element tree, suitable for use in a table
- cell and possibly other situations.
+ def new_a_txBody(cls) -> CT_TextBody:
+ """Return a new `a:txBody` element tree.
+
+ Suitable for use in a table cell and possibly other situations.
"""
xml = cls._a_txBody_tmpl()
- txBody = parse_xml(xml)
+ txBody = cast(CT_TextBody, parse_xml(xml))
return txBody
@classmethod
def new_p_txBody(cls):
- """
- Return a new ```` element tree, suitable for use in an
- ```` element.
- """
+ """Return a new `p:txBody` element tree, suitable for use in an `p:sp` element."""
xml = cls._p_txBody_tmpl()
return parse_xml(xml)
@classmethod
def new_txPr(cls):
- """
- Return a ```` element tree suitable for use in a chart object
- like data labels or tick labels.
+ """Return a `c:txPr` element tree.
+
+ Suitable for use in a chart object like data labels or tick labels.
"""
xml = (
"\n"
@@ -167,8 +170,8 @@ def new_txPr(cls):
def unclear_content(self):
"""Ensure p:txBody has at least one a:p child.
- Intuitively, reverse a ".clear_content()" operation to minimum
- conformance with spec (single empty paragraph).
+ Intuitively, reverse a ".clear_content()" operation to minimum conformance with spec
+ (single empty paragraph).
"""
if len(self.p_lst) > 0:
return
@@ -196,27 +199,43 @@ def _txBody_tmpl(cls):
class CT_TextBodyProperties(BaseOxmlElement):
- """
- custom element class
- """
+ """`a:bodyPr` custom element class."""
+
+ _add_noAutofit: Callable[[], BaseOxmlElement]
+ _add_normAutofit: Callable[[], CT_TextNormalAutofit]
+ _add_spAutoFit: Callable[[], BaseOxmlElement]
+ _remove_eg_textAutoFit: Callable[[], None]
+
+ noAutofit: BaseOxmlElement | None
+ normAutofit: CT_TextNormalAutofit | None
+ spAutoFit: BaseOxmlElement | None
eg_textAutoFit = ZeroOrOneChoice(
(Choice("a:noAutofit"), Choice("a:normAutofit"), Choice("a:spAutoFit")),
successors=("a:scene3d", "a:sp3d", "a:flatTx", "a:extLst"),
)
- lIns = OptionalAttribute("lIns", ST_Coordinate32, default=Emu(91440))
- tIns = OptionalAttribute("tIns", ST_Coordinate32, default=Emu(45720))
- rIns = OptionalAttribute("rIns", ST_Coordinate32, default=Emu(91440))
- bIns = OptionalAttribute("bIns", ST_Coordinate32, default=Emu(45720))
- anchor = OptionalAttribute("anchor", MSO_VERTICAL_ANCHOR)
- wrap = OptionalAttribute("wrap", ST_TextWrappingType)
+ lIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "lIns", ST_Coordinate32, default=Emu(91440)
+ )
+ tIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "tIns", ST_Coordinate32, default=Emu(45720)
+ )
+ rIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "rIns", ST_Coordinate32, default=Emu(91440)
+ )
+ bIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "bIns", ST_Coordinate32, default=Emu(45720)
+ )
+ anchor: MSO_VERTICAL_ANCHOR | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "anchor", MSO_VERTICAL_ANCHOR
+ )
+ wrap: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "wrap", ST_TextWrappingType
+ )
@property
def autofit(self):
- """
- The autofit setting for the text frame, a member of the
- ``MSO_AUTO_SIZE`` enumeration.
- """
+ """The autofit setting for the text frame, a member of the `MSO_AUTO_SIZE` enumeration."""
if self.noAutofit is not None:
return MSO_AUTO_SIZE.NONE
if self.normAutofit is not None:
@@ -226,11 +245,11 @@ def autofit(self):
return None
@autofit.setter
- def autofit(self, value):
+ def autofit(self, value: MSO_AUTO_SIZE | None):
if value is not None and value not in MSO_AUTO_SIZE:
raise ValueError(
- "only None or a member of the MSO_AUTO_SIZE enumeration can "
- "be assigned to CT_TextBodyProperties.autofit, got %s" % value
+ f"only None or a member of the MSO_AUTO_SIZE enumeration can be assigned to"
+ f" CT_TextBodyProperties.autofit, got {value}"
)
self._remove_eg_textAutoFit()
if value == MSO_AUTO_SIZE.NONE:
@@ -242,12 +261,16 @@ def autofit(self, value):
class CT_TextCharacterProperties(BaseOxmlElement):
- """`a:rPr, a:defRPr, and `a:endParaRPr` custom element class.
+ """Custom element class for `a:rPr`, `a:defRPr`, and `a:endParaRPr`.
- 'rPr' is short for 'run properties', and it corresponds to the |Font|
- proxy class.
+ 'rPr' is short for 'run properties', and it corresponds to the |Font| proxy class.
"""
+ get_or_add_hlinkClick: Callable[[], CT_Hyperlink]
+ get_or_add_latin: Callable[[], CT_TextFont]
+ _remove_latin: Callable[[], None]
+ _remove_hlinkClick: Callable[[], None]
+
eg_fillProperties = ZeroOrOneChoice(
(
Choice("a:noFill"),
@@ -275,7 +298,7 @@ class CT_TextCharacterProperties(BaseOxmlElement):
"a:extLst",
),
)
- latin = ZeroOrOne(
+ latin: CT_TextFont | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
"a:latin",
successors=(
"a:ea",
@@ -287,62 +310,73 @@ class CT_TextCharacterProperties(BaseOxmlElement):
"a:extLst",
),
)
- hlinkClick = ZeroOrOne("a:hlinkClick", successors=("a:hlinkMouseOver", "a:rtl", "a:extLst"))
+ hlinkClick: CT_Hyperlink | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:hlinkClick", successors=("a:hlinkMouseOver", "a:rtl", "a:extLst")
+ )
- lang = OptionalAttribute("lang", MSO_LANGUAGE_ID)
- sz = OptionalAttribute("sz", ST_TextFontSize)
- b = OptionalAttribute("b", XsdBoolean)
- i = OptionalAttribute("i", XsdBoolean)
- u = OptionalAttribute("u", MSO_TEXT_UNDERLINE_TYPE)
+ lang: MSO_LANGUAGE_ID | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "lang", MSO_LANGUAGE_ID
+ )
+ sz: int | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "sz", ST_TextFontSize
+ )
+ b: bool | None = OptionalAttribute("b", XsdBoolean) # pyright: ignore[reportAssignmentType]
+ i: bool | None = OptionalAttribute("i", XsdBoolean) # pyright: ignore[reportAssignmentType]
+ u: MSO_TEXT_UNDERLINE_TYPE | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "u", MSO_TEXT_UNDERLINE_TYPE
+ )
def _new_gradFill(self):
return CT_GradientFillProperties.new_gradFill()
- def add_hlinkClick(self, rId):
- """
- Add an child element with r:id attribute set to *rId*.
- """
+ def add_hlinkClick(self, rId: str) -> CT_Hyperlink:
+ """Add an `a:hlinkClick` child element with r:id attribute set to `rId`."""
hlinkClick = self.get_or_add_hlinkClick()
hlinkClick.rId = rId
return hlinkClick
class CT_TextField(BaseOxmlElement):
- """
- field element, for either a slide number or date field
- """
+ """`a:fld` field element, for either a slide number or date field."""
- rPr = ZeroOrOne("a:rPr", successors=("a:pPr", "a:t"))
- t = ZeroOrOne("a:t", successors=())
+ get_or_add_rPr: Callable[[], CT_TextCharacterProperties]
+
+ rPr: CT_TextCharacterProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:rPr", successors=("a:pPr", "a:t")
+ )
+ t: BaseOxmlElement | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:t", successors=()
+ )
@property
- def text(self):
- """
- The text of the ```` child element.
- """
+ def text(self) -> str: # pyright: ignore[reportIncompatibleMethodOverride]
+ """The text of the `a:t` child element."""
t = self.t
if t is None:
return ""
- text = t.text
- return to_unicode(text) if text is not None else ""
+ return t.text or ""
class CT_TextFont(BaseOxmlElement):
- """
- Custom element class for , , , and child
- elements of CT_TextCharacterProperties, e.g. .
+ """Custom element class for `a:latin`, `a:ea`, `a:cs`, and `a:sym`.
+
+ These occur as child elements of CT_TextCharacterProperties, e.g. `a:rPr`.
"""
- typeface = RequiredAttribute("typeface", ST_TextTypeface)
+ typeface: str = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "typeface", ST_TextTypeface
+ )
class CT_TextLineBreak(BaseOxmlElement):
"""`a:br` line break element"""
+ get_or_add_rPr: Callable[[], CT_TextCharacterProperties]
+
rPr = ZeroOrOne("a:rPr", successors=())
@property
- def text(self):
+ def text(self): # pyright: ignore[reportIncompatibleMethodOverride]
"""Unconditionally a single vertical-tab character.
A line break element can contain no text other than the implicit line feed it
@@ -352,9 +386,7 @@ def text(self):
class CT_TextNormalAutofit(BaseOxmlElement):
- """
- element specifying fit text to shape font reduction, etc.
- """
+ """`a:normAutofit` element specifying fit text to shape font reduction, etc."""
fontScale = OptionalAttribute(
"fontScale", ST_TextFontScalePercentOrPercentString, default=100.0
@@ -364,36 +396,42 @@ class CT_TextNormalAutofit(BaseOxmlElement):
class CT_TextParagraph(BaseOxmlElement):
"""`a:p` custom element class"""
- pPr = ZeroOrOne("a:pPr", successors=("a:r", "a:br", "a:fld", "a:endParaRPr"))
+ get_or_add_endParaRPr: Callable[[], CT_TextCharacterProperties]
+ get_or_add_pPr: Callable[[], CT_TextParagraphProperties]
+ r_lst: list[CT_RegularTextRun]
+ _add_br: Callable[[], CT_TextLineBreak]
+ _add_r: Callable[[], CT_RegularTextRun]
+
+ pPr: CT_TextParagraphProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:pPr", successors=("a:r", "a:br", "a:fld", "a:endParaRPr")
+ )
r = ZeroOrMore("a:r", successors=("a:endParaRPr",))
br = ZeroOrMore("a:br", successors=("a:endParaRPr",))
- endParaRPr = ZeroOrOne("a:endParaRPr", successors=())
+ endParaRPr: CT_TextCharacterProperties | None = ZeroOrOne(
+ "a:endParaRPr", successors=()
+ ) # pyright: ignore[reportAssignmentType]
- def add_br(self):
- """
- Return a newly appended element.
- """
+ def add_br(self) -> CT_TextLineBreak:
+ """Return a newly appended `a:br` element."""
return self._add_br()
- def add_r(self, text=None):
- """
- Return a newly appended element.
- """
+ def add_r(self, text: str | None = None) -> CT_RegularTextRun:
+ """Return a newly appended `a:r` element."""
r = self._add_r()
if text:
r.text = text
return r
- def append_text(self, text):
- """Append `a:r` and `a:br` elements to *p* based on *text*.
+ def append_text(self, text: str):
+ """Append `a:r` and `a:br` elements to `p` based on `text`.
- Any `\n` or `\v` (vertical-tab) characters in *text* delimit `a:r` (run)
- elements and themselves are translated to `a:br` (line-break) elements. The
- vertical-tab character appears in clipboard text from PowerPoint at "soft"
- line-breaks (new-line, but not new paragraph).
+ Any `\n` or `\v` (vertical-tab) characters in `text` delimit `a:r` (run) elements and
+ themselves are translated to `a:br` (line-break) elements. The vertical-tab character
+ appears in clipboard text from PowerPoint at "soft" line-breaks (new-line, but not new
+ paragraph).
"""
for idx, r_str in enumerate(re.split("\n|\v", text)):
- # ---breaks are only added *between* items, not at start---
+ # ---breaks are only added _between_ items, not at start---
if idx > 0:
self.add_br()
# ---runs that would be empty are not added---
@@ -401,16 +439,17 @@ def append_text(self, text):
self.add_r(r_str)
@property
- def content_children(self):
+ def content_children(self) -> tuple[CT_RegularTextRun | CT_TextLineBreak | CT_TextField, ...]:
"""Sequence containing text-container child elements of this `a:p` element.
These include `a:r`, `a:br`, and `a:fld`.
"""
- text_types = {CT_RegularTextRun, CT_TextLineBreak, CT_TextField}
- return tuple(elm for elm in self if type(elm) in text_types)
+ return tuple(
+ e for e in self if isinstance(e, (CT_RegularTextRun, CT_TextLineBreak, CT_TextField))
+ )
@property
- def text(self):
+ def text(self) -> str: # pyright: ignore[reportIncompatibleMethodOverride]
"""str text contained in this paragraph."""
# ---note this shadows the lxml _Element.text---
return "".join([child.text for child in self.content_children])
@@ -421,9 +460,15 @@ def _new_r(self):
class CT_TextParagraphProperties(BaseOxmlElement):
- """
- custom element class
- """
+ """`a:pPr` custom element class."""
+
+ get_or_add_defRPr: Callable[[], CT_TextCharacterProperties]
+ _add_lnSpc: Callable[[], CT_TextSpacing]
+ _add_spcAft: Callable[[], CT_TextSpacing]
+ _add_spcBef: Callable[[], CT_TextSpacing]
+ _remove_lnSpc: Callable[[], None]
+ _remove_spcAft: Callable[[], None]
+ _remove_spcBef: Callable[[], None]
_tag_seq = (
"a:lnSpc",
@@ -444,31 +489,43 @@ class CT_TextParagraphProperties(BaseOxmlElement):
"a:defRPr",
"a:extLst",
)
- lnSpc = ZeroOrOne("a:lnSpc", successors=_tag_seq[1:])
- spcBef = ZeroOrOne("a:spcBef", successors=_tag_seq[2:])
- spcAft = ZeroOrOne("a:spcAft", successors=_tag_seq[3:])
- defRPr = ZeroOrOne("a:defRPr", successors=_tag_seq[16:])
- lvl = OptionalAttribute("lvl", ST_TextIndentLevelType, default=0)
- algn = OptionalAttribute("algn", PP_PARAGRAPH_ALIGNMENT)
+ lnSpc: CT_TextSpacing | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:lnSpc", successors=_tag_seq[1:]
+ )
+ spcBef: CT_TextSpacing | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:spcBef", successors=_tag_seq[2:]
+ )
+ spcAft: CT_TextSpacing | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:spcAft", successors=_tag_seq[3:]
+ )
+ defRPr: CT_TextCharacterProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:defRPr", successors=_tag_seq[16:]
+ )
+ lvl: int = OptionalAttribute( # pyright: ignore[reportAssignmentType]
+ "lvl", ST_TextIndentLevelType, default=0
+ )
+ algn: PP_PARAGRAPH_ALIGNMENT | None = OptionalAttribute(
+ "algn", PP_PARAGRAPH_ALIGNMENT
+ ) # pyright: ignore[reportAssignmentType]
del _tag_seq
@property
- def line_spacing(self):
- """
- The spacing between baselines of successive lines in this paragraph.
- A float value indicates a number of lines. A |Length| value indicates
- a fixed spacing. Value is contained in `./a:lnSpc/a:spcPts/@val` or
- `./a:lnSpc/a:spcPct/@val`. Value is |None| if no element is present.
+ def line_spacing(self) -> float | Length | None:
+ """The spacing between baselines of successive lines in this paragraph.
+
+ A float value indicates a number of lines. A |Length| value indicates a fixed spacing.
+ Value is contained in `./a:lnSpc/a:spcPts/@val` or `./a:lnSpc/a:spcPct/@val`. Value is
+ |None| if no element is present.
"""
lnSpc = self.lnSpc
if lnSpc is None:
return None
if lnSpc.spcPts is not None:
return lnSpc.spcPts.val
- return lnSpc.spcPct.val
+ return cast(CT_TextSpacingPercent, lnSpc.spcPct).val
@line_spacing.setter
- def line_spacing(self, value):
+ def line_spacing(self, value: float | Length | None):
self._remove_lnSpc()
if value is None:
return
@@ -478,11 +535,8 @@ def line_spacing(self, value):
self._add_lnSpc().set_spcPct(value)
@property
- def space_after(self):
- """
- The EMU equivalent of the centipoints value in
- `./a:spcAft/a:spcPts/@val`.
- """
+ def space_after(self) -> Length | None:
+ """The EMU equivalent of the centipoints value in `./a:spcAft/a:spcPts/@val`."""
spcAft = self.spcAft
if spcAft is None:
return None
@@ -492,17 +546,14 @@ def space_after(self):
return spcPts.val
@space_after.setter
- def space_after(self, value):
+ def space_after(self, value: Length | None):
self._remove_spcAft()
if value is not None:
self._add_spcAft().set_spcPts(value)
@property
def space_before(self):
- """
- The EMU equivalent of the centipoints value in
- `./a:spcBef/a:spcPts/@val`.
- """
+ """The EMU equivalent of the centipoints value in `./a:spcBef/a:spcPts/@val`."""
spcBef = self.spcBef
if spcBef is None:
return None
@@ -512,54 +563,56 @@ def space_before(self):
return spcPts.val
@space_before.setter
- def space_before(self, value):
+ def space_before(self, value: Length | None):
self._remove_spcBef()
if value is not None:
self._add_spcBef().set_spcPts(value)
class CT_TextSpacing(BaseOxmlElement):
- """
- Used for , , and elements.
- """
+ """Used for `a:lnSpc`, `a:spcBef`, and `a:spcAft` elements."""
+
+ get_or_add_spcPct: Callable[[], CT_TextSpacingPercent]
+ get_or_add_spcPts: Callable[[], CT_TextSpacingPoint]
+ _remove_spcPct: Callable[[], None]
+ _remove_spcPts: Callable[[], None]
# this should actually be a OneAndOnlyOneChoice, but that's not
# implemented yet.
- spcPct = ZeroOrOne("a:spcPct")
- spcPts = ZeroOrOne("a:spcPts")
+ spcPct: CT_TextSpacingPercent | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:spcPct"
+ )
+ spcPts: CT_TextSpacingPoint | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
+ "a:spcPts"
+ )
- def set_spcPct(self, value):
- """
- Set spacing to *value* lines, e.g. 1.75 lines. A ./a:spcPts child is
- removed if present.
+ def set_spcPct(self, value: float):
+ """Set spacing to `value` lines, e.g. 1.75 lines.
+
+ A ./a:spcPts child is removed if present.
"""
self._remove_spcPts()
spcPct = self.get_or_add_spcPct()
spcPct.val = value
- def set_spcPts(self, value):
- """
- Set spacing to *value* points. A ./a:spcPct child is removed if
- present.
- """
+ def set_spcPts(self, value: Length):
+ """Set spacing to `value` points. A ./a:spcPct child is removed if present."""
self._remove_spcPct()
spcPts = self.get_or_add_spcPts()
spcPts.val = value
class CT_TextSpacingPercent(BaseOxmlElement):
- """
- element, specifying spacing in thousandths of a percent in its
- `val` attribute.
- """
+ """`a:spcPct` element, specifying spacing in thousandths of a percent in its `val` attribute."""
- val = RequiredAttribute("val", ST_TextSpacingPercentOrPercentString)
+ val: float = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "val", ST_TextSpacingPercentOrPercentString
+ )
class CT_TextSpacingPoint(BaseOxmlElement):
- """
- element, specifying spacing in centipoints in its `val`
- attribute.
- """
+ """`a:spcPts` element, specifying spacing in centipoints in its `val` attribute."""
- val = RequiredAttribute("val", ST_TextSpacingPoint)
+ val: Length = RequiredAttribute( # pyright: ignore[reportAssignmentType]
+ "val", ST_TextSpacingPoint
+ )
diff --git a/src/pptx/oxml/theme.py b/src/pptx/oxml/theme.py
index 9e3737311..19ac8dea6 100644
--- a/src/pptx/oxml/theme.py
+++ b/src/pptx/oxml/theme.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""lxml custom element classes for theme-related XML elements."""
-"""
-lxml custom element classes for theme-related XML elements.
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from . import parse_from_template
from .xmlchemy import BaseOxmlElement
diff --git a/src/pptx/oxml/xmlchemy.py b/src/pptx/oxml/xmlchemy.py
index b84ef4ddb..41fb2e171 100644
--- a/src/pptx/oxml/xmlchemy.py
+++ b/src/pptx/oxml/xmlchemy.py
@@ -1,36 +1,49 @@
-# encoding: utf-8
+"""Base and meta classes enabling declarative definition of custom element classes."""
-"""
-Base and meta classes that enable declarative definition of custom element
-classes.
-"""
-
-from __future__ import absolute_import, print_function
+from __future__ import annotations
import re
+from typing import Any, Callable, Iterable, Protocol, Sequence, Type, cast
from lxml import etree
+from lxml.etree import ElementBase, _Element # pyright: ignore[reportPrivateUsage]
+
+from pptx.exc import InvalidXmlError
+from pptx.oxml import oxml_parser
+from pptx.oxml.ns import NamespacePrefixedTag, _nsmap, qn # pyright: ignore[reportPrivateUsage]
+from pptx.util import lazyproperty
-from . import oxml_parser
-from ..compat import Unicode
-from ..exc import InvalidXmlError
-from .ns import NamespacePrefixedTag, _nsmap, qn
-from ..util import lazyproperty
+class AttributeType(Protocol):
+ """Interface for an object that can act as an attribute type.
-def OxmlElement(nsptag_str, nsmap=None):
+ An attribute-type specifies how values are transformed to and from the XML "string" value of the
+ attribute.
"""
- Return a 'loose' lxml element having the tag specified by *nsptag_str*.
- *nsptag_str* must contain the standard namespace prefix, e.g. 'a:tbl'.
- The resulting element is an instance of the custom element class for this
- tag name if one is defined.
+
+ @classmethod
+ def from_xml(cls, xml_value: str) -> Any:
+ """Transform an attribute value to a Python value."""
+ ...
+
+ @classmethod
+ def to_xml(cls, value: Any) -> str:
+ """Transform a Python value to a str value suitable to this XML attribute."""
+ ...
+
+
+def OxmlElement(nsptag_str: str, nsmap: dict[str, str] | None = None) -> BaseOxmlElement:
+ """Return a "loose" lxml element having the tag specified by `nsptag_str`.
+
+ `nsptag_str` must contain the standard namespace prefix, e.g. 'a:tbl'. The resulting element is
+ an instance of the custom element class for this tag name if one is defined.
"""
nsptag = NamespacePrefixedTag(nsptag_str)
nsmap = nsmap if nsmap is not None else nsptag.nsmap
return oxml_parser.makeelement(nsptag.clark_name, nsmap=nsmap)
-def serialize_for_reading(element):
+def serialize_for_reading(element: ElementBase):
"""
Serialize *element* to human-readable XML suitable for tests. No XML
declaration.
@@ -39,11 +52,8 @@ def serialize_for_reading(element):
return XmlString(xml)
-class XmlString(Unicode):
- """
- Provides string comparison override suitable for serialized XML that is
- useful for tests.
- """
+class XmlString(str):
+ """Provides string comparison override suitable for serialized XML; useful for tests."""
# ' text'
# | | || |
@@ -53,7 +63,9 @@ class XmlString(Unicode):
_xml_elm_line_patt = re.compile(r"( *?[\w:]+)(.*?)(/?>)([^<]*[\w:]+>)?")
- def __eq__(self, other):
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, str):
+ return False
lines = self.splitlines()
lines_other = other.splitlines()
if len(lines) != len(lines_other):
@@ -63,22 +75,22 @@ def __eq__(self, other):
return False
return True
- def __ne__(self, other):
+ def __ne__(self, other: object) -> bool:
return not self.__eq__(other)
- def _attr_seq(self, attrs):
- """
- Return a sequence of attribute strings parsed from *attrs*. Each
- attribute string is stripped of whitespace on both ends.
+ def _attr_seq(self, attrs: str) -> list[str]:
+ """Return a sequence of attribute strings parsed from *attrs*.
+
+ Each attribute string is stripped of whitespace on both ends.
"""
attrs = attrs.strip()
attr_lst = attrs.split()
return sorted(attr_lst)
- def _eq_elm_strs(self, line, line_2):
- """
- Return True if the element in *line_2* is XML equivalent to the
- element in *line*.
+ def _eq_elm_strs(self, line: str, line_2: str) -> bool:
+ """True if the element in `line_2` is XML-equivalent to the element in `line`.
+
+ In particular, the order of attributes in XML is not significant.
"""
front, attrs, close, text = self._parse_line(line)
front_2, attrs_2, close_2, text_2 = self._parse_line(line_2)
@@ -92,22 +104,19 @@ def _eq_elm_strs(self, line, line_2):
return False
return True
- def _parse_line(self, line):
- """
- Return front, attrs, close, text 4-tuple result of parsing XML element
- string *line*.
- """
+ def _parse_line(self, line: str):
+ """Return front, attrs, close, text 4-tuple result of parsing XML element string `line`."""
match = self._xml_elm_line_patt.match(line)
+ if match is None:
+ raise ValueError("`line` does not match pattern for an XML element")
front, attrs, close, text = [match.group(n) for n in range(1, 5)]
return front, attrs, close, text
class MetaOxmlElement(type):
- """
- Metaclass for BaseOxmlElement
- """
+ """Metaclass for BaseOxmlElement."""
- def __init__(cls, clsname, bases, clsdict):
+ def __init__(cls, clsname: str, bases: tuple[type, ...], clsdict: dict[str, Any]):
dispatchable = (
OneAndOnlyOne,
OneOrMore,
@@ -122,18 +131,14 @@ def __init__(cls, clsname, bases, clsdict):
value.populate_class_members(cls, key)
-class BaseAttribute(object):
- """
- Base class for OptionalAttribute and RequiredAttribute, providing common
- methods.
- """
+class BaseAttribute:
+ """Base class for OptionalAttribute and RequiredAttribute, providing common methods."""
- def __init__(self, attr_name, simple_type):
- super(BaseAttribute, self).__init__()
+ def __init__(self, attr_name: str, simple_type: type[AttributeType]):
self._attr_name = attr_name
self._simple_type = simple_type
- def populate_class_members(self, element_cls, prop_name):
+ def populate_class_members(self, element_cls: Type[BaseOxmlElement], prop_name: str):
"""
Add the appropriate methods to *element_cls*.
"""
@@ -143,10 +148,10 @@ def populate_class_members(self, element_cls, prop_name):
self._add_attr_property()
def _add_attr_property(self):
- """
- Add a read/write ``{prop_name}`` property to the element class that
- returns the interpreted value of this attribute on access and changes
- the attribute value to its ST_* counterpart on assignment.
+ """Add a read/write `{prop_name}` property to the element class.
+
+ The property returns the interpreted value of this attribute on access and changes the
+ attribute value to its ST_* counterpart on assignment.
"""
property_ = property(self._getter, self._setter, None)
# assign unconditionally to overwrite element name definition
@@ -158,15 +163,25 @@ def _clark_name(self):
return qn(self._attr_name)
return self._attr_name
+ @property
+ def _getter(self) -> Callable[[BaseOxmlElement], Any]:
+ """Callable suitable for the "get" side of the attribute property descriptor."""
+ raise NotImplementedError("must be implemented by each subclass")
+
+ @property
+ def _setter(self) -> Callable[[BaseOxmlElement, Any], None]:
+ """Callable suitable for the "set" side of the attribute property descriptor."""
+ raise NotImplementedError("must be implemented by each subclass")
+
class OptionalAttribute(BaseAttribute):
- """
- Defines an optional attribute on a custom element class. An optional
- attribute returns a default value when not present for reading. When
- assigned |None|, the attribute is removed.
+ """Defines an optional attribute on a custom element class.
+
+ An optional attribute returns a default value when not present for reading. When assigned
+ |None|, the attribute is removed.
"""
- def __init__(self, attr_name, simple_type, default=None):
+ def __init__(self, attr_name: str, simple_type: type[AttributeType], default: Any = None):
super(OptionalAttribute, self).__init__(attr_name, simple_type)
self._default = default
@@ -184,13 +199,10 @@ def _docstring(self):
)
@property
- def _getter(self):
- """
- Return a function object suitable for the "get" side of the attribute
- property descriptor.
- """
+ def _getter(self) -> Callable[[BaseOxmlElement], Any]:
+ """Callable suitable for the "get" side of the attribute property descriptor."""
- def get_attr_value(obj):
+ def get_attr_value(obj: BaseOxmlElement) -> Any:
attr_str_value = obj.get(self._clark_name)
if attr_str_value is None:
return self._default
@@ -200,13 +212,12 @@ def get_attr_value(obj):
return get_attr_value
@property
- def _setter(self):
- """
- Return a function object suitable for the "set" side of the attribute
- property descriptor.
- """
+ def _setter(self) -> Callable[[BaseOxmlElement, Any], None]:
+ """Callable suitable for the "set" side of the attribute property descriptor."""
- def set_attr_value(obj, value):
+ def set_attr_value(obj: BaseOxmlElement, value: Any) -> None:
+ # -- when an XML attribute has a default value, setting it to that default removes the
+ # -- attribute from the element (when it is present)
if value == self._default:
if self._clark_name in obj.attrib:
del obj.attrib[self._clark_name]
@@ -218,28 +229,23 @@ def set_attr_value(obj, value):
class RequiredAttribute(BaseAttribute):
- """
- Defines a required attribute on a custom element class. A required
- attribute is assumed to be present for reading, so does not have
- a default value; its actual value is always used. If missing on read,
- an |InvalidXmlError| is raised. It also does not remove the attribute if
- |None| is assigned. Assigning |None| raises |TypeError| or |ValueError|,
- depending on the simple type of the attribute.
+ """Defines a required attribute on a custom element class.
+
+ A required attribute is assumed to be present for reading, so does not have a default value;
+ its actual value is always used. If missing on read, an |InvalidXmlError| is raised. It also
+ does not remove the attribute if |None| is assigned. Assigning |None| raises |TypeError| or
+ |ValueError|, depending on the simple type of the attribute.
"""
@property
- def _getter(self):
- """
- Return a function object suitable for the "get" side of the attribute
- property descriptor.
- """
+ def _getter(self) -> Callable[[BaseOxmlElement], Any]:
+ """Callable suitable for the "get" side of the attribute property descriptor."""
- def get_attr_value(obj):
+ def get_attr_value(obj: BaseOxmlElement) -> Any:
attr_str_value = obj.get(self._clark_name)
if attr_str_value is None:
raise InvalidXmlError(
- "required '%s' attribute not present on element %s"
- % (self._attr_name, obj.tag)
+ "required '%s' attribute not present on element %s" % (self._attr_name, obj.tag)
)
return self._simple_type.from_xml(attr_str_value)
@@ -258,45 +264,36 @@ def _docstring(self):
)
@property
- def _setter(self):
- """
- Return a function object suitable for the "set" side of the attribute
- property descriptor.
- """
+ def _setter(self) -> Callable[[BaseOxmlElement, Any], None]:
+ """Callable suitable for the "set" side of the attribute property descriptor."""
- def set_attr_value(obj, value):
+ def set_attr_value(obj: BaseOxmlElement, value: Any) -> None:
str_value = self._simple_type.to_xml(value)
obj.set(self._clark_name, str_value)
return set_attr_value
-class _BaseChildElement(object):
- """
- Base class for the child element classes corresponding to varying
- cardinalities, such as ZeroOrOne and ZeroOrMore.
+class _BaseChildElement:
+ """Base class for the child element classes corresponding to varying cardinalities.
+
+ Subclasses include ZeroOrOne and ZeroOrMore.
"""
- def __init__(self, nsptagname, successors=()):
+ def __init__(self, nsptagname: str, successors: Sequence[str] = ()):
super(_BaseChildElement, self).__init__()
self._nsptagname = nsptagname
self._successors = successors
- def populate_class_members(self, element_cls, prop_name):
- """
- Baseline behavior for adding the appropriate methods to
- *element_cls*.
- """
+ def populate_class_members(self, element_cls: Type[BaseOxmlElement], prop_name: str):
+ """Baseline behavior for adding the appropriate methods to `element_cls`."""
self._element_cls = element_cls
self._prop_name = prop_name
def _add_adder(self):
- """
- Add an ``_add_x()`` method to the element class for this child
- element.
- """
+ """Add an ``_add_x()`` method to the element class for this child element."""
- def _add_child(obj, **attrs):
+ def _add_child(obj: BaseOxmlElement, **attrs: Any):
new_method = getattr(obj, self._new_method_name)
child = new_method()
for key, value in attrs.items():
@@ -312,9 +309,9 @@ def _add_child(obj, **attrs):
self._add_to_class(self._add_method_name, _add_child)
def _add_creator(self):
- """
- Add a ``_new_{prop_name}()`` method to the element class that creates
- a new, empty element of the correct type, having no attributes.
+ """Add a `_new_{prop_name}()` method to the element class.
+
+ This method creates a new, empty element of the correct type, having no attributes.
"""
creator = self._creator
creator.__doc__ = (
@@ -324,21 +321,18 @@ def _add_creator(self):
self._add_to_class(self._new_method_name, creator)
def _add_getter(self):
- """
- Add a read-only ``{prop_name}`` property to the element class for
- this child element.
+ """Add a read-only `{prop_name}` property to the parent element class.
+
+ The property locates and returns this child element or `None` if not present.
"""
property_ = property(self._getter, None, None)
# assign unconditionally to overwrite element name definition
setattr(self._element_cls, self._prop_name, property_)
def _add_inserter(self):
- """
- Add an ``_insert_x()`` method to the element class for this child
- element.
- """
+ """Add an ``_insert_x()`` method to the element class for this child element."""
- def _insert_child(obj, child):
+ def _insert_child(obj: BaseOxmlElement, child: BaseOxmlElement):
obj.insert_element_before(child, *self._successors)
return child
@@ -353,7 +347,7 @@ def _add_list_getter(self):
Add a read-only ``{prop_name}_lst`` property to the element class to
retrieve a list of child elements matching this type.
"""
- prop_name = "%s_lst" % self._prop_name
+ prop_name = f"{self._prop_name}_lst"
property_ = property(self._list_getter, None, None)
setattr(self._element_cls, prop_name, property_)
@@ -361,36 +355,30 @@ def _add_list_getter(self):
def _add_method_name(self):
return "_add_%s" % self._prop_name
- def _add_to_class(self, name, method):
- """
- Add *method* to the target class as *name*, unless *name* is already
- defined on the class.
- """
+ def _add_to_class(self, name: str, method: Callable[..., Any]):
+ """Add `method` to the target class as `name`, unless `name` is already defined there."""
if hasattr(self._element_cls, name):
return
setattr(self._element_cls, name, method)
@property
- def _creator(self):
- """
- Return a function object that creates a new, empty element of the
- right type, having no attributes.
- """
+ def _creator(self) -> Callable[[BaseOxmlElement], BaseOxmlElement]:
+ """Callable that creates a new, empty element of the child type, having no attributes."""
- def new_child_element(obj):
+ def new_child_element(obj: BaseOxmlElement):
return OxmlElement(self._nsptagname)
return new_child_element
@property
- def _getter(self):
- """
- Return a function object suitable for the "get" side of the property
- descriptor. This default getter returns the child element with
- matching tag name or |None| if not present.
+ def _getter(self) -> Callable[[BaseOxmlElement], BaseOxmlElement | None]:
+ """Callable suitable for the "get" side of the property descriptor.
+
+ This default getter returns the child element with matching tag name or |None| if not
+ present.
"""
- def get_child_element(obj):
+ def get_child_element(obj: BaseOxmlElement) -> BaseOxmlElement | None:
return obj.find(qn(self._nsptagname))
get_child_element.__doc__ = (
@@ -403,14 +391,11 @@ def _insert_method_name(self):
return "_insert_%s" % self._prop_name
@property
- def _list_getter(self):
- """
- Return a function object suitable for the "get" side of a list
- property descriptor.
- """
+ def _list_getter(self) -> Callable[[BaseOxmlElement], list[BaseOxmlElement]]:
+ """Callable suitable for the "get" side of a list property descriptor."""
- def get_child_element_list(obj):
- return obj.findall(qn(self._nsptagname))
+ def get_child_element_list(obj: BaseOxmlElement) -> list[BaseOxmlElement]:
+ return cast("list[BaseOxmlElement]", obj.findall(qn(self._nsptagname)))
get_child_element_list.__doc__ = (
"A list containing each of the ``<%s>`` child elements, in the o"
@@ -428,19 +413,16 @@ def _new_method_name(self):
class Choice(_BaseChildElement):
- """
- Defines a child element belonging to a group, only one of which may
- appear as a child.
- """
+ """Defines a child element belonging to a group, only one of which may appear as a child."""
@property
def nsptagname(self):
return self._nsptagname
- def populate_class_members(self, element_cls, group_prop_name, successors):
- """
- Add the appropriate methods to *element_cls*.
- """
+ def populate_class_members( # pyright: ignore[reportIncompatibleMethodOverride]
+ self, element_cls: Type[BaseOxmlElement], group_prop_name: str, successors: Sequence[str]
+ ):
+ """Add the appropriate methods to `element_cls`."""
self._element_cls = element_cls
self._group_prop_name = group_prop_name
self._successors = successors
@@ -451,13 +433,10 @@ def populate_class_members(self, element_cls, group_prop_name, successors):
self._add_adder()
self._add_get_or_change_to_method()
- def _add_get_or_change_to_method(self):
- """
- Add a ``get_or_change_to_x()`` method to the element class for this
- child element.
- """
+ def _add_get_or_change_to_method(self) -> None:
+ """Add a `get_or_change_to_x()` method to the element class for this child element."""
- def get_or_change_to_child(obj):
+ def get_or_change_to_child(obj: BaseOxmlElement):
child = getattr(obj, self._prop_name)
if child is not None:
return child
@@ -493,14 +472,12 @@ def _remove_group_method_name(self):
class OneAndOnlyOne(_BaseChildElement):
- """
- Defines a required child element for MetaOxmlElement.
- """
+ """Defines a required child element for MetaOxmlElement."""
- def __init__(self, nsptagname):
- super(OneAndOnlyOne, self).__init__(nsptagname, None)
+ def __init__(self, nsptagname: str):
+ super(OneAndOnlyOne, self).__init__(nsptagname, ())
- def populate_class_members(self, element_cls, prop_name):
+ def populate_class_members(self, element_cls: Type[BaseOxmlElement], prop_name: str):
"""
Add the appropriate methods to *element_cls*.
"""
@@ -508,13 +485,10 @@ def populate_class_members(self, element_cls, prop_name):
self._add_getter()
@property
- def _getter(self):
- """
- Return a function object suitable for the "get" side of the property
- descriptor.
- """
+ def _getter(self) -> Callable[[BaseOxmlElement], BaseOxmlElement]:
+ """Callable suitable for the "get" side of the property descriptor."""
- def get_child_element(obj):
+ def get_child_element(obj: BaseOxmlElement) -> BaseOxmlElement:
child = obj.find(qn(self._nsptagname))
if child is None:
raise InvalidXmlError(
@@ -522,22 +496,15 @@ def get_child_element(obj):
)
return child
- get_child_element.__doc__ = (
- "Required ``<%s>`` child element." % self._nsptagname
- )
+ get_child_element.__doc__ = "Required ``<%s>`` child element." % self._nsptagname
return get_child_element
class OneOrMore(_BaseChildElement):
- """
- Defines a repeating child element for MetaOxmlElement that must appear at
- least once.
- """
+ """Defines a repeating child element for MetaOxmlElement that must appear at least once."""
- def populate_class_members(self, element_cls, prop_name):
- """
- Add the appropriate methods to *element_cls*.
- """
+ def populate_class_members(self, element_cls: Type[BaseOxmlElement], prop_name: str):
+ """Add the appropriate methods to *element_cls*."""
super(OneOrMore, self).populate_class_members(element_cls, prop_name)
self._add_list_getter()
self._add_creator()
@@ -546,12 +513,10 @@ def populate_class_members(self, element_cls, prop_name):
self._add_public_adder()
delattr(element_cls, prop_name)
- def _add_public_adder(self):
- """
- Add a public ``add_x()`` method to the parent element class.
- """
+ def _add_public_adder(self) -> None:
+ """Add a public `.add_x()` method to the parent element class."""
- def add_child(obj):
+ def add_child(obj: BaseOxmlElement) -> BaseOxmlElement:
private_add_method = getattr(obj, self._add_method_name)
child = private_add_method()
return child
@@ -578,7 +543,7 @@ class ZeroOrMore(_BaseChildElement):
Defines an optional repeating child element for MetaOxmlElement.
"""
- def populate_class_members(self, element_cls, prop_name):
+ def populate_class_members(self, element_cls: Type[BaseOxmlElement], prop_name: str):
"""
Add the appropriate methods to *element_cls*.
"""
@@ -591,14 +556,10 @@ def populate_class_members(self, element_cls, prop_name):
class ZeroOrOne(_BaseChildElement):
- """
- Defines an optional child element for MetaOxmlElement.
- """
+ """Defines an optional child element for MetaOxmlElement."""
- def populate_class_members(self, element_cls, prop_name):
- """
- Add the appropriate methods to *element_cls*.
- """
+ def populate_class_members(self, element_cls: Type[BaseOxmlElement], prop_name: str):
+ """Add the appropriate methods to `element_cls`."""
super(ZeroOrOne, self).populate_class_members(element_cls, prop_name)
self._add_getter()
self._add_creator()
@@ -608,12 +569,9 @@ def populate_class_members(self, element_cls, prop_name):
self._add_remover()
def _add_get_or_adder(self):
- """
- Add a ``get_or_add_x()`` method to the element class for this
- child element.
- """
+ """Add a `.get_or_add_x()` method to the element class for this child element."""
- def get_or_add_child(obj):
+ def get_or_add_child(obj: BaseOxmlElement) -> BaseOxmlElement:
child = getattr(obj, self._prop_name)
if child is None:
add_method = getattr(obj, self._add_method_name)
@@ -626,17 +584,12 @@ def get_or_add_child(obj):
self._add_to_class(self._get_or_add_method_name, get_or_add_child)
def _add_remover(self):
- """
- Add a ``_remove_x()`` method to the element class for this child
- element.
- """
+ """Add a `._remove_x()` method to the element class for this child element."""
- def _remove_child(obj):
+ def _remove_child(obj: BaseOxmlElement) -> None:
obj.remove_all(self._nsptagname)
- _remove_child.__doc__ = (
- "Remove all ``<%s>`` child elements."
- ) % self._nsptagname
+ _remove_child.__doc__ = f"Remove all `{self._nsptagname}` child elements."
self._add_to_class(self._remove_method_name, _remove_child)
@lazyproperty
@@ -645,50 +598,37 @@ def _get_or_add_method_name(self):
class ZeroOrOneChoice(_BaseChildElement):
- """
- Correspondes to an ``EG_*`` element group where at most one of its
- members may appear as a child.
- """
+ """An `EG_*` element group where at most one of its members may appear as a child."""
- def __init__(self, choices, successors=()):
- self._choices = choices
- self._successors = successors
+ def __init__(self, choices: Iterable[Choice], successors: Iterable[str] = ()):
+ self._choices = tuple(choices)
+ self._successors = tuple(successors)
- def populate_class_members(self, element_cls, prop_name):
- """
- Add the appropriate methods to *element_cls*.
- """
+ def populate_class_members(self, element_cls: Type[BaseOxmlElement], prop_name: str):
+ """Add the appropriate methods to `element_cls`."""
super(ZeroOrOneChoice, self).populate_class_members(element_cls, prop_name)
self._add_choice_getter()
for choice in self._choices:
- choice.populate_class_members(
- element_cls, self._prop_name, self._successors
- )
+ choice.populate_class_members(element_cls, self._prop_name, self._successors)
self._add_group_remover()
def _add_choice_getter(self):
- """
- Add a read-only ``{prop_name}`` property to the element class that
- returns the present member of this group, or |None| if none are
- present.
+ """Add a read-only `.{prop_name}` property to the element class.
+
+ The property returns the present member of this group, or |None| if none are present.
"""
property_ = property(self._choice_getter, None, None)
# assign unconditionally to overwrite element name definition
setattr(self._element_cls, self._prop_name, property_)
def _add_group_remover(self):
- """
- Add a ``_remove_eg_x()`` method to the element class for this choice
- group.
- """
+ """Add a `._remove_eg_x()` method to the element class for this choice group."""
- def _remove_choice_group(obj):
+ def _remove_choice_group(obj: BaseOxmlElement) -> None:
for tagname in self._member_nsptagnames:
obj.remove_all(tagname)
- _remove_choice_group.__doc__ = (
- "Remove the current choice group child element if present."
- )
+ _remove_choice_group.__doc__ = "Remove the current choice group child element if present."
self._add_to_class(self._remove_choice_group_method_name, _remove_choice_group)
@property
@@ -698,8 +638,10 @@ def _choice_getter(self):
descriptor.
"""
- def get_group_member_element(obj):
- return obj.first_child_found_in(*self._member_nsptagnames)
+ def get_group_member_element(obj: BaseOxmlElement) -> BaseOxmlElement | None:
+ return cast(
+ "BaseOxmlElement | None", obj.first_child_found_in(*self._member_nsptagnames)
+ )
get_group_member_element.__doc__ = (
"Return the child element belonging to this element group, or "
@@ -708,49 +650,39 @@ def get_group_member_element(obj):
return get_group_member_element
@lazyproperty
- def _member_nsptagnames(self):
- """
- Sequence of namespace-prefixed tagnames, one for each of the member
- elements of this choice group.
- """
+ def _member_nsptagnames(self) -> list[str]:
+ """Sequence of namespace-prefixed tagnames, one for each member element of choice group."""
return [choice.nsptagname for choice in self._choices]
@lazyproperty
def _remove_choice_group_method_name(self):
- return "_remove_%s" % self._prop_name
+ """Function-name for choice remover."""
+ return f"_remove_{self._prop_name}"
-class _OxmlElementBase(etree.ElementBase):
- """
- Provides common behavior for oxml element classes
- """
+# -- lxml typing isn't quite right here, just ignore this error on _Element --
+class BaseOxmlElement(etree.ElementBase, metaclass=MetaOxmlElement):
+ """Effective base class for all custom element classes.
- @classmethod
- def child_tagnames_after(cls, tagname):
- """
- Return a sequence containing the namespace prefixed child tagnames,
- e.g. 'a:prstGeom', that occur after *tagname* in this element.
- """
- return cls.child_tagnames.tagnames_after(tagname)
+ Adds standardized behavior to all classes in one place.
+ """
- def delete(self):
- """
- Remove this element from the XML tree.
- """
- self.getparent().remove(self)
+ def __repr__(self):
+ return "<%s '<%s>' at 0x%0x>" % (
+ self.__class__.__name__,
+ self._nsptag,
+ id(self),
+ )
- def first_child_found_in(self, *tagnames):
- """
- Return the first child found with tag in *tagnames*, or None if
- not found.
- """
+ def first_child_found_in(self, *tagnames: str) -> _Element | None:
+ """First child with tag in `tagnames`, or None if not found."""
for tagname in tagnames:
child = self.find(qn(tagname))
if child is not None:
return child
return None
- def insert_element_before(self, elm, *tagnames):
+ def insert_element_before(self, elm: ElementBase, *tagnames: str):
successor = self.first_child_found_in(*tagnames)
if successor is not None:
successor.addprevious(elm)
@@ -758,40 +690,28 @@ def insert_element_before(self, elm, *tagnames):
self.append(elm)
return elm
- def remove_all(self, tagname):
- """
- Remove all child elements having *tagname*.
- """
- matching = self.findall(qn(tagname))
- for child in matching:
- self.remove(child)
-
- def remove_if_present(self, *tagnames):
- """
- Remove all child elements having tagname in *tagnames*.
- """
+ def remove_all(self, *tagnames: str) -> None:
+ """Remove child elements with tagname (e.g. "a:p") in `tagnames`."""
for tagname in tagnames:
- element = self.find(qn(tagname))
- if element is not None:
- self.remove(element)
+ matching = self.findall(qn(tagname))
+ for child in matching:
+ self.remove(child)
@property
- def xml(self):
- """
- Return XML string for this element, suitable for testing purposes.
- Pretty printed for readability and without an XML declaration at the
- top.
+ def xml(self) -> str:
+ """XML string for this element, suitable for testing purposes.
+
+ Pretty printed for readability and without an XML declaration at the top.
"""
return serialize_for_reading(self)
- def xpath(self, xpath_str):
- """
- Override of ``lxml`` _Element.xpath() method to provide standard Open
- XML namespace mapping in centralized location.
- """
- return super(BaseOxmlElement, self).xpath(xpath_str, namespaces=_nsmap)
+ def xpath(self, xpath_str: str) -> Any: # pyright: ignore[reportIncompatibleMethodOverride]
+ """Override of `lxml` _Element.xpath() method.
+ Provides standard Open XML namespace mapping (`nsmap`) in centralized location.
+ """
+ return super().xpath(xpath_str, namespaces=_nsmap)
-BaseOxmlElement = MetaOxmlElement(
- "BaseOxmlElement", (etree.ElementBase,), dict(_OxmlElementBase.__dict__)
-)
+ @property
+ def _nsptag(self) -> str:
+ return NamespacePrefixedTag.from_clark_name(self.tag)
diff --git a/src/pptx/package.py b/src/pptx/package.py
index 1d5e73cd6..79703cd6c 100644
--- a/src/pptx/package.py
+++ b/src/pptx/package.py
@@ -1,7 +1,9 @@
-# encoding: utf-8
-
"""Overall .pptx package."""
+from __future__ import annotations
+
+from typing import IO, Iterator
+
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.opc.package import OpcPackage
from pptx.opc.packuri import PackURI
@@ -15,7 +17,7 @@ class Package(OpcPackage):
"""An overall .pptx package."""
@lazyproperty
- def core_properties(self):
+ def core_properties(self) -> CorePropertiesPart:
"""Instance of |CoreProperties| holding read/write Dublin Core doc properties.
Creates a default core properties part if one is not present (not common).
@@ -27,7 +29,7 @@ def core_properties(self):
self.relate_to(core_props, RT.CORE_PROPERTIES)
return core_props
- def get_or_add_image_part(self, image_file):
+ def get_or_add_image_part(self, image_file: str | IO[bytes]):
"""
Return an |ImagePart| object containing the image in *image_file*. If
the image part already exists in this package, it is reused,
@@ -43,10 +45,10 @@ def get_or_add_media_part(self, media):
"""
return self._media_parts.get_or_add_media_part(media)
- def next_image_partname(self, ext):
- """
- Return a |PackURI| instance representing the next available image
- partname, by sequence number. *ext* is used as the extention on the
+ def next_image_partname(self, ext: str) -> PackURI:
+ """Return a |PackURI| instance representing the next available image partname.
+
+ Partname uses the next available sequence number. *ext* is used as the extention on the
returned partname.
"""
@@ -127,10 +129,8 @@ def __init__(self, package):
super(_ImageParts, self).__init__()
self._package = package
- def __iter__(self):
- """
- Generate a reference to each |ImagePart| object in the package.
- """
+ def __iter__(self) -> Iterator[ImagePart]:
+ """Generate a reference to each |ImagePart| object in the package."""
image_parts = []
for rel in self._package.iter_rels():
if rel.is_external:
@@ -143,7 +143,7 @@ def __iter__(self):
image_parts.append(image_part)
yield image_part
- def get_or_add_image_part(self, image_file):
+ def get_or_add_image_part(self, image_file: str | IO[bytes]) -> ImagePart:
"""Return |ImagePart| object containing the image in `image_file`.
`image_file` can be either a path to an image file or a file-like object
@@ -152,9 +152,9 @@ def get_or_add_image_part(self, image_file):
"""
image = Image.from_file(image_file)
image_part = self._find_by_sha1(image.sha1)
- return ImagePart.new(self._package, image) if image_part is None else image_part
+ return image_part if image_part else ImagePart.new(self._package, image)
- def _find_by_sha1(self, sha1):
+ def _find_by_sha1(self, sha1: str) -> ImagePart | None:
"""
Return an |ImagePart| object belonging to this package or |None| if
no matching image part is found. The image part is identified by the
diff --git a/src/pptx/parts/chart.py b/src/pptx/parts/chart.py
index 2a8a04283..7208071b4 100644
--- a/src/pptx/parts/chart.py
+++ b/src/pptx/parts/chart.py
@@ -1,13 +1,21 @@
-# encoding: utf-8
-
"""Chart part objects, including Chart and Charts."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
from pptx.chart.chart import Chart
-from pptx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TYPE as RT
+from pptx.opc.constants import CONTENT_TYPE as CT
+from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.opc.package import XmlPart
from pptx.parts.embeddedpackage import EmbeddedXlsxPart
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.chart.data import ChartData
+ from pptx.enum.chart import XL_CHART_TYPE
+ from pptx.package import Package
+
class ChartPart(XmlPart):
"""A chart part.
@@ -18,7 +26,7 @@ class ChartPart(XmlPart):
partname_template = "/ppt/charts/chart%d.xml"
@classmethod
- def new(cls, chart_type, chart_data, package):
+ def new(cls, chart_type: XL_CHART_TYPE, chart_data: ChartData, package: Package):
"""Return new |ChartPart| instance added to `package`.
Returned chart-part contains a chart of `chart_type` depicting `chart_data`.
@@ -74,11 +82,7 @@ def xlsx_part(self):
is |None| if there is no `` element.
"""
xlsx_part_rId = self._chartSpace.xlsx_part_rId
- return (
- None
- if xlsx_part_rId is None
- else self._chart_part.related_part(xlsx_part_rId)
- )
+ return None if xlsx_part_rId is None else self._chart_part.related_part(xlsx_part_rId)
@xlsx_part.setter
def xlsx_part(self, xlsx_part):
diff --git a/src/pptx/parts/coreprops.py b/src/pptx/parts/coreprops.py
index e39b154d0..8471cc8ef 100644
--- a/src/pptx/parts/coreprops.py
+++ b/src/pptx/parts/coreprops.py
@@ -3,12 +3,16 @@
from __future__ import annotations
import datetime as dt
+from typing import TYPE_CHECKING
from pptx.opc.constants import CONTENT_TYPE as CT
from pptx.opc.package import XmlPart
from pptx.opc.packuri import PackURI
from pptx.oxml.coreprops import CT_CoreProperties
+if TYPE_CHECKING:
+ from pptx.package import Package
+
class CorePropertiesPart(XmlPart):
"""Corresponds to part named `/docProps/core.xml`.
@@ -16,8 +20,10 @@ class CorePropertiesPart(XmlPart):
Contains the core document properties for this document package.
"""
+ _element: CT_CoreProperties
+
@classmethod
- def default(cls, package):
+ def default(cls, package: Package):
"""Return default new |CorePropertiesPart| instance suitable as starting point.
This provides a base for adding core-properties to a package that doesn't yet
@@ -31,35 +37,35 @@ def default(cls, package):
return core_props
@property
- def author(self):
+ def author(self) -> str:
return self._element.author_text
@author.setter
- def author(self, value):
+ def author(self, value: str):
self._element.author_text = value
@property
- def category(self):
+ def category(self) -> str:
return self._element.category_text
@category.setter
- def category(self, value):
+ def category(self, value: str):
self._element.category_text = value
@property
- def comments(self):
+ def comments(self) -> str:
return self._element.comments_text
@comments.setter
- def comments(self, value):
+ def comments(self, value: str):
self._element.comments_text = value
@property
- def content_status(self):
+ def content_status(self) -> str:
return self._element.contentStatus_text
@content_status.setter
- def content_status(self, value):
+ def content_status(self, value: str):
self._element.contentStatus_text = value
@property
@@ -67,39 +73,39 @@ def created(self):
return self._element.created_datetime
@created.setter
- def created(self, value):
+ def created(self, value: dt.datetime):
self._element.created_datetime = value
@property
- def identifier(self):
+ def identifier(self) -> str:
return self._element.identifier_text
@identifier.setter
- def identifier(self, value):
+ def identifier(self, value: str):
self._element.identifier_text = value
@property
- def keywords(self):
+ def keywords(self) -> str:
return self._element.keywords_text
@keywords.setter
- def keywords(self, value):
+ def keywords(self, value: str):
self._element.keywords_text = value
@property
- def language(self):
+ def language(self) -> str:
return self._element.language_text
@language.setter
- def language(self, value):
+ def language(self, value: str):
self._element.language_text = value
@property
- def last_modified_by(self):
+ def last_modified_by(self) -> str:
return self._element.lastModifiedBy_text
@last_modified_by.setter
- def last_modified_by(self, value):
+ def last_modified_by(self, value: str):
self._element.lastModifiedBy_text = value
@property
@@ -107,7 +113,7 @@ def last_printed(self):
return self._element.lastPrinted_datetime
@last_printed.setter
- def last_printed(self, value):
+ def last_printed(self, value: dt.datetime):
self._element.lastPrinted_datetime = value
@property
@@ -115,7 +121,7 @@ def modified(self):
return self._element.modified_datetime
@modified.setter
- def modified(self, value):
+ def modified(self, value: dt.datetime):
self._element.modified_datetime = value
@property
@@ -123,35 +129,35 @@ def revision(self):
return self._element.revision_number
@revision.setter
- def revision(self, value):
+ def revision(self, value: int):
self._element.revision_number = value
@property
- def subject(self):
+ def subject(self) -> str:
return self._element.subject_text
@subject.setter
- def subject(self, value):
+ def subject(self, value: str):
self._element.subject_text = value
@property
- def title(self):
+ def title(self) -> str:
return self._element.title_text
@title.setter
- def title(self, value):
+ def title(self, value: str):
self._element.title_text = value
@property
- def version(self):
+ def version(self) -> str:
return self._element.version_text
@version.setter
- def version(self, value):
+ def version(self, value: str):
self._element.version_text = value
@classmethod
- def _new(cls, package):
+ def _new(cls, package: Package) -> CorePropertiesPart:
"""Return new empty |CorePropertiesPart| instance."""
return CorePropertiesPart(
PackURI("/docProps/core.xml"),
diff --git a/src/pptx/parts/embeddedpackage.py b/src/pptx/parts/embeddedpackage.py
index c2d434e04..7aa2cf408 100644
--- a/src/pptx/parts/embeddedpackage.py
+++ b/src/pptx/parts/embeddedpackage.py
@@ -1,14 +1,19 @@
-# encoding: utf-8
-
"""Embedded Package part objects.
"Package" in this context means another OPC package, i.e. a DOCX, PPTX, or XLSX "file".
"""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
from pptx.enum.shapes import PROG_ID
from pptx.opc.constants import CONTENT_TYPE as CT
from pptx.opc.package import Part
+if TYPE_CHECKING:
+ from pptx.package import Package
+
class EmbeddedPackagePart(Part):
"""A distinct OPC package, e.g. an Excel file, embedded in this PPTX package.
@@ -17,7 +22,7 @@ class EmbeddedPackagePart(Part):
"""
@classmethod
- def factory(cls, prog_id, object_blob, package):
+ def factory(cls, prog_id: PROG_ID | str, object_blob: bytes, package: Package):
"""Return a new |EmbeddedPackagePart| subclass instance added to *package*.
The subclass is determined by `prog_id` which corresponds to the "application"
@@ -43,7 +48,7 @@ def factory(cls, prog_id, object_blob, package):
return EmbeddedPartCls.new(object_blob, package)
@classmethod
- def new(cls, blob, package):
+ def new(cls, blob: bytes, package: Package):
"""Return new |EmbeddedPackagePart| subclass object.
The returned part object contains `blob` and is added to `package`.
diff --git a/src/pptx/parts/image.py b/src/pptx/parts/image.py
index db59c5fcc..9be5d02d6 100644
--- a/src/pptx/parts/image.py
+++ b/src/pptx/parts/image.py
@@ -1,36 +1,44 @@
-# encoding: utf-8
-
"""ImagePart and related objects."""
-from __future__ import division
+from __future__ import annotations
import hashlib
+import io
import os
+from typing import IO, TYPE_CHECKING, Any, cast
-try:
- from PIL import Image as PIL_Image
-except ImportError:
- import Image as PIL_Image
+from PIL import Image as PIL_Image
-from pptx.compat import BytesIO, is_string
from pptx.opc.package import Part
from pptx.opc.spec import image_content_types
-from pptx.util import lazyproperty
+from pptx.util import Emu, lazyproperty
+
+if TYPE_CHECKING:
+ from pptx.opc.packuri import PackURI
+ from pptx.package import Package
+ from pptx.util import Length
class ImagePart(Part):
"""An image part.
- An image part generally has a partname matching the regex
- `ppt/media/image[1-9][0-9]*.*`.
+ An image part generally has a partname matching the regex `ppt/media/image[1-9][0-9]*.*`.
"""
- def __init__(self, partname, content_type, package, blob, filename=None):
+ def __init__(
+ self,
+ partname: PackURI,
+ content_type: str,
+ package: Package,
+ blob: bytes,
+ filename: str | None = None,
+ ):
super(ImagePart, self).__init__(partname, content_type, package, blob)
+ self._blob = blob
self._filename = filename
@classmethod
- def new(cls, package, image):
+ def new(cls, package: Package, image: Image) -> ImagePart:
"""Return new |ImagePart| instance containing `image`.
`image` is an |Image| object.
@@ -44,80 +52,76 @@ def new(cls, package, image):
)
@property
- def desc(self):
- """
- The filename associated with this image, either the filename of
- the original image or a generic name of the form ``image.ext``
- where ``ext`` is appropriate to the image file format, e.g.
- ``'jpg'``. An image created using a path will have that filename; one
- created with a file-like object will have a generic name.
+ def desc(self) -> str:
+ """The filename associated with this image.
+
+ Either the filename of the original image or a generic name of the form `image.ext` where
+ `ext` is appropriate to the image file format, e.g. `'jpg'`. An image created using a path
+ will have that filename; one created with a file-like object will have a generic name.
"""
- # return generic filename if original filename is unknown
+ # -- return generic filename if original filename is unknown --
if self._filename is None:
- return "image.%s" % self.ext
+ return f"image.{self.ext}"
return self._filename
@property
- def ext(self):
- """
- Return file extension for this image e.g. ``'png'``.
- """
+ def ext(self) -> str:
+ """File-name extension for this image e.g. `'png'`."""
return self.partname.ext
@property
- def image(self):
- """
- An |Image| object containing the image in this image part.
- """
- return Image(self.blob, self.desc)
+ def image(self) -> Image:
+ """An |Image| object containing the image in this image part.
- def scale(self, scaled_cx, scaled_cy):
+ Note this is a `pptx.image.Image` object, not a PIL Image.
"""
- Return scaled image dimensions in EMU based on the combination of
- parameters supplied. If *scaled_cx* and *scaled_cy* are both |None|,
- the native image size is returned. If neither *scaled_cx* nor
- *scaled_cy* is |None|, their values are returned unchanged. If
- a value is provided for either *scaled_cx* or *scaled_cy* and the
- other is |None|, the missing value is calculated such that the
- image's aspect ratio is preserved.
+ return Image(self._blob, self.desc)
+
+ def scale(self, scaled_cx: int | None, scaled_cy: int | None) -> tuple[int, int]:
+ """Return scaled image dimensions in EMU based on the combination of parameters supplied.
+
+ If `scaled_cx` and `scaled_cy` are both |None|, the native image size is returned. If
+ neither `scaled_cx` nor `scaled_cy` is |None|, their values are returned unchanged. If a
+ value is provided for either `scaled_cx` or `scaled_cy` and the other is |None|, the
+ missing value is calculated such that the image's aspect ratio is preserved.
"""
image_cx, image_cy = self._native_size
- if scaled_cx is None and scaled_cy is None:
- scaled_cx = image_cx
- scaled_cy = image_cy
- elif scaled_cx is None:
- scaling_factor = float(scaled_cy) / float(image_cy)
- scaled_cx = int(round(image_cx * scaling_factor))
- elif scaled_cy is None:
+ if scaled_cx and scaled_cy:
+ return scaled_cx, scaled_cy
+
+ if scaled_cx and not scaled_cy:
scaling_factor = float(scaled_cx) / float(image_cx)
scaled_cy = int(round(image_cy * scaling_factor))
+ return scaled_cx, scaled_cy
+
+ if not scaled_cx and scaled_cy:
+ scaling_factor = float(scaled_cy) / float(image_cy)
+ scaled_cx = int(round(image_cx * scaling_factor))
+ return scaled_cx, scaled_cy
- return scaled_cx, scaled_cy
+ # -- only remaining case is both `scaled_cx` and `scaled_cy` are `None` --
+ return image_cx, image_cy
@lazyproperty
- def sha1(self):
- """
- The SHA1 hash digest for the image binary of this image part, like:
- ``'1be010ea47803b00e140b852765cdf84f491da47'``.
+ def sha1(self) -> str:
+ """The 40-character SHA1 hash digest for the image binary of this image part.
+
+ like: `"1be010ea47803b00e140b852765cdf84f491da47"`.
"""
return hashlib.sha1(self._blob).hexdigest()
@property
- def _dpi(self):
- """
- A (horz_dpi, vert_dpi) 2-tuple (ints) representing the dots-per-inch
- property of this image.
- """
- image = Image.from_blob(self.blob)
+ def _dpi(self) -> tuple[int, int]:
+ """(horz_dpi, vert_dpi) pair representing the dots-per-inch resolution of this image."""
+ image = Image.from_blob(self._blob)
return image.dpi
@property
- def _native_size(self):
- """
- A (width, height) 2-tuple representing the native dimensions of the
- image in EMU, calculated based on the image DPI value, if present,
- assuming 72 dpi as a default.
+ def _native_size(self) -> tuple[Length, Length]:
+ """A (width, height) 2-tuple representing the native dimensions of the image in EMU.
+
+ Calculated based on the image DPI value, if present, assuming 72 dpi as a default.
"""
EMU_PER_INCH = 914400
horz_dpi, vert_dpi = self._dpi
@@ -126,38 +130,35 @@ def _native_size(self):
width = EMU_PER_INCH * width_px / horz_dpi
height = EMU_PER_INCH * height_px / vert_dpi
- return width, height
+ return Emu(int(width)), Emu(int(height))
@property
- def _px_size(self):
- """
- A (width, height) 2-tuple representing the dimensions of this image
- in pixels.
- """
- image = Image.from_blob(self.blob)
+ def _px_size(self) -> tuple[int, int]:
+ """A (width, height) 2-tuple representing the dimensions of this image in pixels."""
+ image = Image.from_blob(self._blob)
return image.size
class Image(object):
"""Immutable value object representing an image such as a JPEG, PNG, or GIF."""
- def __init__(self, blob, filename):
+ def __init__(self, blob: bytes, filename: str | None):
super(Image, self).__init__()
self._blob = blob
self._filename = filename
@classmethod
- def from_blob(cls, blob, filename=None):
- """Return a new |Image| object loaded from the image binary in *blob*."""
+ def from_blob(cls, blob: bytes, filename: str | None = None) -> Image:
+ """Return a new |Image| object loaded from the image binary in `blob`."""
return cls(blob, filename)
@classmethod
- def from_file(cls, image_file):
- """
- Return a new |Image| object loaded from *image_file*, which can be
- either a path (string) or a file-like object.
+ def from_file(cls, image_file: str | IO[bytes]) -> Image:
+ """Return a new |Image| object loaded from `image_file`.
+
+ `image_file` can be either a path (str) or a file-like object.
"""
- if is_string(image_file):
+ if isinstance(image_file, str):
# treat image_file as a path
with open(image_file, "rb") as f:
blob = f.read()
@@ -173,32 +174,27 @@ def from_file(cls, image_file):
return cls.from_blob(blob, filename)
@property
- def blob(self):
- """
- The binary image bytestream of this image.
- """
+ def blob(self) -> bytes:
+ """The binary image bytestream of this image."""
return self._blob
@lazyproperty
- def content_type(self):
- """
- MIME-type of this image, e.g. ``'image/jpeg'``.
- """
+ def content_type(self) -> str:
+ """MIME-type of this image, e.g. `"image/jpeg"`."""
return image_content_types[self.ext]
@lazyproperty
- def dpi(self):
- """
- A (horz_dpi, vert_dpi) 2-tuple specifying the dots-per-inch
- resolution of this image. A default value of (72, 72) is used if the
- dpi is not specified in the image file.
+ def dpi(self) -> tuple[int, int]:
+ """A (horz_dpi, vert_dpi) 2-tuple specifying the dots-per-inch resolution of this image.
+
+ A default value of (72, 72) is used if the dpi is not specified in the image file.
"""
- def int_dpi(dpi):
- """
- Return an integer dots-per-inch value corresponding to *dpi*. If
- *dpi* is |None|, a non-numeric type, less than 1 or greater than
- 2048, 72 is returned.
+ def int_dpi(dpi: Any):
+ """Return an integer dots-per-inch value corresponding to `dpi`.
+
+ If `dpi` is |None|, a non-numeric type, less than 1 or greater than 2048, 72 is
+ returned.
"""
try:
int_dpi = int(round(float(dpi)))
@@ -208,12 +204,11 @@ def int_dpi(dpi):
int_dpi = 72
return int_dpi
- def normalize_pil_dpi(pil_dpi):
- """
- Return a (horz_dpi, vert_dpi) 2-tuple corresponding to *pil_dpi*,
- the value for the 'dpi' key in the ``info`` dict of a PIL image.
- If the 'dpi' key is not present or contains an invalid value,
- ``(72, 72)`` is returned.
+ def normalize_pil_dpi(pil_dpi: tuple[int, int] | None):
+ """Return a (horz_dpi, vert_dpi) 2-tuple corresponding to `pil_dpi`.
+
+ The value for the 'dpi' key in the `info` dict of a PIL image. If the 'dpi' key is not
+ present or contains an invalid value, `(72, 72)` is returned.
"""
if isinstance(pil_dpi, tuple):
return (int_dpi(pil_dpi[0]), int_dpi(pil_dpi[1]))
@@ -222,12 +217,11 @@ def normalize_pil_dpi(pil_dpi):
return normalize_pil_dpi(self._pil_props[2])
@lazyproperty
- def ext(self):
- """
- Canonical file extension for this image e.g. ``'png'``. The returned
- extension is all lowercase and is the canonical extension for the
- content type of this image, regardless of what extension may have
- been used in its filename, if any.
+ def ext(self) -> str:
+ """Canonical file extension for this image e.g. `'png'`.
+
+ The returned extension is all lowercase and is the canonical extension for the content type
+ of this image, regardless of what extension may have been used in its filename, if any.
"""
ext_map = {
"BMP": "bmp",
@@ -244,46 +238,38 @@ def ext(self):
return ext_map[format]
@property
- def filename(self):
- """
- The filename from the path from which this image was loaded, if
- loaded from the filesystem. |None| if no filename was used in
- loading, such as when loaded from an in-memory stream.
+ def filename(self) -> str | None:
+ """Filename from path used to load this image, if loaded from the filesystem.
+
+ |None| if no filename was used in loading, such as when loaded from an in-memory stream.
"""
return self._filename
@lazyproperty
- def sha1(self):
- """
- SHA1 hash digest of the image blob
- """
+ def sha1(self) -> str:
+ """SHA1 hash digest of the image blob."""
return hashlib.sha1(self._blob).hexdigest()
@lazyproperty
- def size(self):
- """
- A (width, height) 2-tuple specifying the dimensions of this image in
- pixels.
- """
+ def size(self) -> tuple[int, int]:
+ """A (width, height) 2-tuple specifying the dimensions of this image in pixels."""
return self._pil_props[1]
@property
- def _format(self):
- """
- The PIL Image format of this image, e.g. 'PNG'.
- """
+ def _format(self) -> str | None:
+ """The PIL Image format of this image, e.g. 'PNG'."""
return self._pil_props[0]
@lazyproperty
- def _pil_props(self):
- """
- A tuple containing useful image properties extracted from this image
- using Pillow (Python Imaging Library, or 'PIL').
- """
- stream = BytesIO(self._blob)
- pil_image = PIL_Image.open(stream)
+ def _pil_props(self) -> tuple[str | None, tuple[int, int], tuple[int, int] | None]:
+ """tuple of image properties extracted from this image using Pillow."""
+ stream = io.BytesIO(self._blob)
+ pil_image = PIL_Image.open(stream) # pyright: ignore[reportUnknownMemberType]
format = pil_image.format
width_px, height_px = pil_image.size
- dpi = pil_image.info.get("dpi")
+ dpi = cast(
+ "tuple[int, int] | None",
+ pil_image.info.get("dpi"), # pyright: ignore[reportUnknownMemberType]
+ )
stream.close()
return (format, (width_px, height_px), dpi)
diff --git a/src/pptx/parts/media.py b/src/pptx/parts/media.py
index 81efb5a5d..7e8bc2f21 100644
--- a/src/pptx/parts/media.py
+++ b/src/pptx/parts/media.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""MediaPart and related objects."""
+from __future__ import annotations
+
import hashlib
from pptx.opc.package import Part
diff --git a/src/pptx/parts/presentation.py b/src/pptx/parts/presentation.py
index 30b4ff016..1413de457 100644
--- a/src/pptx/parts/presentation.py
+++ b/src/pptx/parts/presentation.py
@@ -1,7 +1,9 @@
-# encoding: utf-8
-
"""Presentation part, the main part in a .pptx package."""
+from __future__ import annotations
+
+from typing import IO, TYPE_CHECKING, Iterable
+
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.opc.package import XmlPart
from pptx.opc.packuri import PackURI
@@ -9,6 +11,10 @@
from pptx.presentation import Presentation
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.parts.coreprops import CorePropertiesPart
+ from pptx.slide import NotesMaster, Slide, SlideLayout, SlideMaster
+
class PresentationPart(XmlPart):
"""Top level class in object model.
@@ -16,10 +22,10 @@ class PresentationPart(XmlPart):
Represents the contents of the /ppt directory of a .pptx file.
"""
- def add_slide(self, slide_layout):
- """
- Return an (rId, slide) pair of a newly created blank slide that
- inherits appearance from *slide_layout*.
+ def add_slide(self, slide_layout: SlideLayout):
+ """Return (rId, slide) pair of a newly created blank slide.
+
+ New slide inherits appearance from `slide_layout`.
"""
partname = self._next_slide_partname
slide_layout_part = slide_layout.part
@@ -28,14 +34,14 @@ def add_slide(self, slide_layout):
return rId, slide_part.slide
@property
- def core_properties(self):
- """
- A |CoreProperties| object providing read/write access to the core
- properties of this presentation.
+ def core_properties(self) -> CorePropertiesPart:
+ """A |CoreProperties| object for the presentation.
+
+ Provides read/write access to the Dublin Core properties of this presentation.
"""
return self.package.core_properties
- def get_slide(self, slide_id):
+ def get_slide(self, slide_id: int) -> Slide | None:
"""Return optional related |Slide| object identified by `slide_id`.
Returns |None| if no slide with `slide_id` is related to this presentation.
@@ -46,7 +52,7 @@ def get_slide(self, slide_id):
return None
@lazyproperty
- def notes_master(self):
+ def notes_master(self) -> NotesMaster:
"""
Return the |NotesMaster| object for this presentation. If the
presentation does not have a notes master, one is created from
@@ -56,12 +62,11 @@ def notes_master(self):
return self.notes_master_part.notes_master
@lazyproperty
- def notes_master_part(self):
- """
- Return the |NotesMasterPart| object for this presentation. If the
- presentation does not have a notes master, one is created from
- a default template. The same single instance is returned on each
- call.
+ def notes_master_part(self) -> NotesMasterPart:
+ """Return the |NotesMasterPart| object for this presentation.
+
+ If the presentation does not have a notes master, one is created from a default template.
+ The same single instance is returned on each call.
"""
try:
return self.part_related_by(RT.NOTES_MASTER)
@@ -78,27 +83,27 @@ def presentation(self):
"""
return Presentation(self._element, self)
- def related_slide(self, rId):
+ def related_slide(self, rId: str) -> Slide:
"""Return |Slide| object for related |SlidePart| related by `rId`."""
return self.related_part(rId).slide
- def related_slide_master(self, rId):
+ def related_slide_master(self, rId: str) -> SlideMaster:
"""Return |SlideMaster| object for |SlideMasterPart| related by `rId`."""
return self.related_part(rId).slide_master
- def rename_slide_parts(self, rIds):
+ def rename_slide_parts(self, rIds: Iterable[str]):
"""Assign incrementing partnames to the slide parts identified by `rIds`.
- Partnames are like `/ppt/slides/slide9.xml` and are assigned in the order their
- id appears in the `rIds` sequence. The name portion is always ``slide``. The
- number part forms a continuous sequence starting at 1 (e.g. 1, 2, ... 10, ...).
- The extension is always ``.xml``.
+ Partnames are like `/ppt/slides/slide9.xml` and are assigned in the order their id appears
+ in the `rIds` sequence. The name portion is always `slide`. The number part forms a
+ continuous sequence starting at 1 (e.g. 1, 2, ... 10, ...). The extension is always
+ `.xml`.
"""
for idx, rId in enumerate(rIds):
slide_part = self.related_part(rId)
slide_part.partname = PackURI("/ppt/slides/slide%d.xml" % (idx + 1))
- def save(self, path_or_stream):
+ def save(self, path_or_stream: str | IO[bytes]):
"""Save this presentation package to `path_or_stream`.
`path_or_stream` can be either a path to a filesystem location (a string) or a
diff --git a/src/pptx/parts/slide.py b/src/pptx/parts/slide.py
index 5d721bb41..6650564a5 100644
--- a/src/pptx/parts/slide.py
+++ b/src/pptx/parts/slide.py
@@ -1,9 +1,12 @@
-# encoding: utf-8
-
"""Slide and related objects."""
+from __future__ import annotations
+
+from typing import IO, TYPE_CHECKING, cast
+
from pptx.enum.shapes import PROG_ID
-from pptx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TYPE as RT
+from pptx.opc.constants import CONTENT_TYPE as CT
+from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.opc.package import XmlPart
from pptx.opc.packuri import PackURI
from pptx.oxml.slide import CT_NotesMaster, CT_NotesSlide, CT_Slide
@@ -13,6 +16,12 @@
from pptx.slide import NotesMaster, NotesSlide, Slide, SlideLayout, SlideMaster
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.chart.data import ChartData
+ from pptx.enum.chart import XL_CHART_TYPE
+ from pptx.media import Video
+ from pptx.parts.image import Image, ImagePart
+
class BaseSlidePart(XmlPart):
"""Base class for slide parts.
@@ -21,15 +30,17 @@ class BaseSlidePart(XmlPart):
notes-master, and handout-master parts.
"""
- def get_image(self, rId):
- """
- Return an |Image| object containing the image related to this slide
- by *rId*. Raises |KeyError| if no image is related by that id, which
- would generally indicate a corrupted .pptx file.
+ _element: CT_Slide
+
+ def get_image(self, rId: str) -> Image:
+ """Return an |Image| object containing the image related to this slide by *rId*.
+
+ Raises |KeyError| if no image is related by that id, which would generally indicate a
+ corrupted .pptx file.
"""
- return self.related_part(rId).image
+ return cast("ImagePart", self.related_part(rId)).image
- def get_or_add_image_part(self, image_file):
+ def get_or_add_image_part(self, image_file: str | IO[bytes]):
"""Return `(image_part, rId)` pair corresponding to `image_file`.
The returned |ImagePart| object contains the image in `image_file` and is
@@ -41,10 +52,8 @@ def get_or_add_image_part(self, image_file):
return image_part, rId
@property
- def name(self):
- """
- Internal name of this slide.
- """
+ def name(self) -> str:
+ """Internal name of this slide."""
return self._element.cSld.name
@@ -159,7 +168,7 @@ def new(cls, partname, package, slide_layout_part):
slide_part.relate_to(slide_layout_part, RT.SLIDE_LAYOUT)
return slide_part
- def add_chart_part(self, chart_type, chart_data):
+ def add_chart_part(self, chart_type: XL_CHART_TYPE, chart_data: ChartData):
"""Return str rId of new |ChartPart| object containing chart of `chart_type`.
The chart depicts `chart_data` and is related to the slide contained in this
@@ -167,7 +176,9 @@ def add_chart_part(self, chart_type, chart_data):
"""
return self.relate_to(ChartPart.new(chart_type, chart_data, self._package), RT.CHART)
- def add_embedded_ole_object_part(self, prog_id, ole_object_file):
+ def add_embedded_ole_object_part(
+ self, prog_id: PROG_ID | str, ole_object_file: str | IO[bytes]
+ ):
"""Return rId of newly-added OLE-object part formed from `ole_object_file`."""
relationship_type = RT.PACKAGE if isinstance(prog_id, PROG_ID) else RT.OLE_OBJECT
return self.relate_to(
@@ -177,7 +188,7 @@ def add_embedded_ole_object_part(self, prog_id, ole_object_file):
relationship_type,
)
- def get_or_add_video_media_part(self, video):
+ def get_or_add_video_media_part(self, video: Video) -> tuple[str, str]:
"""Return rIds for media and video relationships to media part.
A new |MediaPart| object is created if it does not already exist
@@ -207,11 +218,11 @@ def has_notes_slide(self):
return True
@lazyproperty
- def notes_slide(self):
- """
- The |NotesSlide| instance associated with this slide. If the slide
- does not have a notes slide, a new one is created. The same single
- instance is returned on each call.
+ def notes_slide(self) -> NotesSlide:
+ """The |NotesSlide| instance associated with this slide.
+
+ If the slide does not have a notes slide, a new one is created. The same single instance
+ is returned on each call.
"""
try:
notes_slide_part = self.part_related_by(RT.NOTES_SLIDE)
@@ -227,19 +238,14 @@ def slide(self):
return Slide(self._element, self)
@property
- def slide_id(self):
- """
- Return the slide identifier stored in the presentation part for this
- slide part.
- """
+ def slide_id(self) -> int:
+ """Return the slide identifier stored in the presentation part for this slide part."""
presentation_part = self.package.presentation_part
return presentation_part.slide_id(self)
@property
- def slide_layout(self):
- """
- |SlideLayout| object the slide in this part inherits from.
- """
+ def slide_layout(self) -> SlideLayout:
+ """|SlideLayout| object the slide in this part inherits appearance from."""
slide_layout_part = self.part_related_by(RT.SLIDE_LAYOUT)
return slide_layout_part.slide_layout
@@ -268,10 +274,8 @@ def slide_layout(self):
return SlideLayout(self._element, self)
@property
- def slide_master(self):
- """
- Slide master from which this slide layout inherits properties.
- """
+ def slide_master(self) -> SlideMaster:
+ """Slide master from which this slide layout inherits properties."""
return self.part_related_by(RT.SLIDE_MASTER).slide_master
@@ -281,11 +285,8 @@ class SlideMasterPart(BaseSlidePart):
Corresponds to package files ppt/slideMasters/slideMaster[1-9][0-9]*.xml.
"""
- def related_slide_layout(self, rId):
- """
- Return the |SlideLayout| object of the related |SlideLayoutPart|
- corresponding to relationship key *rId*.
- """
+ def related_slide_layout(self, rId: str) -> SlideLayout:
+ """Return |SlideLayout| related to this slide-master by key `rId`."""
return self.related_part(rId).slide_layout
@lazyproperty
diff --git a/src/pptx/presentation.py b/src/pptx/presentation.py
index eabcda72c..a41bfd59a 100644
--- a/src/pptx/presentation.py
+++ b/src/pptx/presentation.py
@@ -1,11 +1,19 @@
-# encoding: utf-8
-
"""Main presentation object."""
+from __future__ import annotations
+
+from typing import IO, TYPE_CHECKING, cast
+
from pptx.shared import PartElementProxy
from pptx.slide import SlideMasters, Slides
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.oxml.presentation import CT_Presentation, CT_SlideId
+ from pptx.parts.presentation import PresentationPart
+ from pptx.slide import NotesMaster, SlideLayouts
+ from pptx.util import Length
+
class Presentation(PartElementProxy):
"""PresentationML (PML) presentation.
@@ -14,34 +22,37 @@ class Presentation(PartElementProxy):
create a presentation.
"""
+ _element: CT_Presentation
+ part: PresentationPart # pyright: ignore[reportIncompatibleMethodOverride]
+
@property
def core_properties(self):
- """
- Instance of |CoreProperties| holding the read/write Dublin Core
- document properties for this presentation.
+ """|CoreProperties| instance for this presentation.
+
+ Provides read/write access to the Dublin Core document properties for the presentation.
"""
return self.part.core_properties
@property
- def notes_master(self):
- """
- Instance of |NotesMaster| for this presentation. If the presentation
- does not have a notes master, one is created from a default template
+ def notes_master(self) -> NotesMaster:
+ """Instance of |NotesMaster| for this presentation.
+
+ If the presentation does not have a notes master, one is created from a default template
and returned. The same single instance is returned on each call.
"""
return self.part.notes_master
- def save(self, file):
- """
- Save this presentation to *file*, where *file* can be either a path
- to a file (a string) or a file-like object.
+ def save(self, file: str | IO[bytes]):
+ """Writes this presentation to `file`.
+
+ `file` can be either a file-path or a file-like object open for writing bytes.
"""
self.part.save(file)
@property
- def slide_height(self):
- """
- Height of slides in this presentation, in English Metric Units (EMU).
+ def slide_height(self) -> Length | None:
+ """Height of slides in this presentation, in English Metric Units (EMU).
+
Returns |None| if no slide width is defined. Read/write.
"""
sldSz = self._element.sldSz
@@ -50,18 +61,17 @@ def slide_height(self):
return sldSz.cy
@slide_height.setter
- def slide_height(self, height):
+ def slide_height(self, height: Length):
sldSz = self._element.get_or_add_sldSz()
sldSz.cy = height
@property
- def slide_layouts(self):
- """
- Sequence of |SlideLayout| instances belonging to the first
- |SlideMaster| of this presentation. A presentation can have more than
- one slide master and each master will have its own set of layouts.
- This property is a convenience for the common case where the
- presentation has only a single slide master.
+ def slide_layouts(self) -> SlideLayouts:
+ """|SlideLayouts| collection belonging to the first |SlideMaster| of this presentation.
+
+ A presentation can have more than one slide master and each master will have its own set
+ of layouts. This property is a convenience for the common case where the presentation has
+ only a single slide master.
"""
return self.slide_masters[0].slide_layouts
@@ -75,10 +85,8 @@ def slide_master(self):
return self.slide_masters[0]
@lazyproperty
- def slide_masters(self):
- """
- Sequence of |SlideMaster| objects belonging to this presentation
- """
+ def slide_masters(self) -> SlideMasters:
+ """|SlideMasters| collection of slide-masters belonging to this presentation."""
return SlideMasters(self._element.get_or_add_sldMasterIdLst(), self)
@property
@@ -93,15 +101,13 @@ def slide_width(self):
return sldSz.cx
@slide_width.setter
- def slide_width(self, width):
+ def slide_width(self, width: Length):
sldSz = self._element.get_or_add_sldSz()
sldSz.cx = width
@lazyproperty
def slides(self):
- """
- |Slides| object containing the slides in this presentation.
- """
+ """|Slides| object containing the slides in this presentation."""
sldIdLst = self._element.get_or_add_sldIdLst()
- self.part.rename_slide_parts([sldId.rId for sldId in sldIdLst])
+ self.part.rename_slide_parts([cast("CT_SlideId", sldId).rId for sldId in sldIdLst])
return Slides(sldIdLst, self)
diff --git a/src/pptx/shapes/__init__.py b/src/pptx/shapes/__init__.py
index c8e1f24d9..332109a31 100644
--- a/src/pptx/shapes/__init__.py
+++ b/src/pptx/shapes/__init__.py
@@ -1,25 +1,26 @@
-# encoding: utf-8
+"""Objects used across sub-package."""
-"""
-Objects used across sub-package
-"""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from pptx.opc.package import XmlPart
+ from pptx.types import ProvidesPart
class Subshape(object):
- """
- Provides common services for drawing elements that occur below a shape
- but may occasionally require an ancestor object to provide a service,
- such as add or drop a relationship. Provides ``self._parent`` attribute
- to subclasses.
+ """Provides access to the containing part for drawing elements that occur below a shape.
+
+ Access to the part is required for example to add or drop a relationship. Provides
+ `self._parent` attribute to subclasses.
"""
- def __init__(self, parent):
+ def __init__(self, parent: ProvidesPart):
super(Subshape, self).__init__()
self._parent = parent
@property
- def part(self):
- """
- The package part containing this object
- """
+ def part(self) -> XmlPart:
+ """The package part containing this object."""
return self._parent.part
diff --git a/src/pptx/shapes/autoshape.py b/src/pptx/shapes/autoshape.py
index ead5fecb5..c7f8cd93e 100644
--- a/src/pptx/shapes/autoshape.py
+++ b/src/pptx/shapes/autoshape.py
@@ -1,11 +1,10 @@
-# encoding: utf-8
-
"""Autoshape-related objects such as Shape and Adjustment."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from numbers import Number
-import xml.sax.saxutils as saxutils
+from typing import TYPE_CHECKING, Iterable
+from xml.sax import saxutils
from pptx.dml.fill import FillFormat
from pptx.dml.line import LineFormat
@@ -15,110 +14,104 @@
from pptx.text.text import TextFrame
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.oxml.shapes.autoshape import CT_GeomGuide, CT_PresetGeometry2D, CT_Shape
+ from pptx.spec import AdjustmentValue
+ from pptx.types import ProvidesPart
-class Adjustment(object):
- """
- An adjustment value for an autoshape.
- An adjustment value corresponds to the position of an adjustment handle on
- an auto shape. Adjustment handles are the small yellow diamond-shaped
- handles that appear on certain auto shapes and allow the outline of the
- shape to be adjusted. For example, a rounded rectangle has an adjustment
- handle that allows the radius of its corner rounding to be adjusted.
+class Adjustment:
+ """An adjustment value for an autoshape.
- Values are |float| and generally range from 0.0 to 1.0, although the value
- can be negative or greater than 1.0 in certain circumstances.
+ An adjustment value corresponds to the position of an adjustment handle on an auto shape.
+ Adjustment handles are the small yellow diamond-shaped handles that appear on certain auto
+ shapes and allow the outline of the shape to be adjusted. For example, a rounded rectangle has
+ an adjustment handle that allows the radius of its corner rounding to be adjusted.
+
+ Values are |float| and generally range from 0.0 to 1.0, although the value can be negative or
+ greater than 1.0 in certain circumstances.
"""
- def __init__(self, name, def_val, actual=None):
+ def __init__(self, name: str, def_val: int, actual: int | None = None):
super(Adjustment, self).__init__()
self.name = name
self.def_val = def_val
self.actual = actual
@property
- def effective_value(self):
- """
- Read/write |float| representing normalized adjustment value for this
- adjustment. Actual values are a large-ish integer expressed in shape
- coordinates, nominally between 0 and 100,000. The effective value is
- normalized to a corresponding value nominally between 0.0 and 1.0.
- Intuitively this represents the proportion of the width or height of
- the shape at which the adjustment value is located from its starting
- point. For simple shapes such as a rounded rectangle, this intuitive
- correspondence holds. For more complicated shapes and at more extreme
- shape proportions (e.g. width is much greater than height), the value
- can become negative or greater than 1.0.
- """
- raw_value = self.actual
- if raw_value is None:
- raw_value = self.def_val
+ def effective_value(self) -> float:
+ """Read/write |float| representing normalized adjustment value for this adjustment.
+
+ Actual values are a large-ish integer expressed in shape coordinates, nominally between 0
+ and 100,000. The effective value is normalized to a corresponding value nominally between
+ 0.0 and 1.0. Intuitively this represents the proportion of the width or height of the shape
+ at which the adjustment value is located from its starting point. For simple shapes such as
+ a rounded rectangle, this intuitive correspondence holds. For more complicated shapes and
+ at more extreme shape proportions (e.g. width is much greater than height), the value can
+ become negative or greater than 1.0.
+ """
+ raw_value = self.actual if self.actual is not None else self.def_val
return self._normalize(raw_value)
@effective_value.setter
- def effective_value(self, value):
+ def effective_value(self, value: float):
if not isinstance(value, Number):
- tmpl = "adjustment value must be numeric, got '%s'"
- raise ValueError(tmpl % value)
+ raise ValueError(f"adjustment value must be numeric, got {repr(value)}")
self.actual = self._denormalize(value)
@staticmethod
- def _denormalize(value):
- """
- Return integer corresponding to normalized *raw_value* on unit basis
- of 100,000. See Adjustment.normalize for additional details.
+ def _denormalize(value: float) -> int:
+ """Return integer corresponding to normalized `raw_value` on unit basis of 100,000.
+
+ See Adjustment.normalize for additional details.
"""
return int(value * 100000.0)
@staticmethod
- def _normalize(raw_value):
- """
- Return normalized value for *raw_value*. A normalized value is a
- |float| between 0.0 and 1.0 for nominal raw values between 0 and
- 100,000. Raw values less than 0 and greater than 100,000 are valid
- and return values calculated on the same unit basis of 100,000.
+ def _normalize(raw_value: int) -> float:
+ """Return normalized value for `raw_value`.
+
+ A normalized value is a |float| between 0.0 and 1.0 for nominal raw values between 0 and
+ 100,000. Raw values less than 0 and greater than 100,000 are valid and return values
+ calculated on the same unit basis of 100,000.
"""
return raw_value / 100000.0
@property
- def val(self):
- """
- Denormalized effective value (expressed in shape coordinates),
- suitable for using in the XML.
+ def val(self) -> int:
+ """Denormalized effective value.
+
+ Expressed in shape coordinates, this is suitable for using in the XML.
"""
return self.actual if self.actual is not None else self.def_val
-class AdjustmentCollection(object):
- """
- Sequence of |Adjustment| instances for an auto shape, each representing
- an available adjustment for a shape of its type. Supports ``len()`` and
- indexed access, e.g. ``shape.adjustments[1] = 0.15``.
+class AdjustmentCollection:
+ """Sequence of |Adjustment| instances for an auto shape.
+
+ Each represents an available adjustment for a shape of its type. Supports `len()` and indexed
+ access, e.g. `shape.adjustments[1] = 0.15`.
"""
- def __init__(self, prstGeom):
+ def __init__(self, prstGeom: CT_PresetGeometry2D):
super(AdjustmentCollection, self).__init__()
self._adjustments_ = self._initialized_adjustments(prstGeom)
self._prstGeom = prstGeom
- def __getitem__(self, key):
+ def __getitem__(self, idx: int) -> float:
"""Provides indexed access, (e.g. 'adjustments[9]')."""
- return self._adjustments_[key].effective_value
+ return self._adjustments_[idx].effective_value
- def __setitem__(self, key, value):
- """
- Provides item assignment via an indexed expression, e.g.
- ``adjustments[9] = 999.9``. Causes all adjustment values in
- collection to be written to the XML.
+ def __setitem__(self, idx: int, value: float):
+ """Provides item assignment via an indexed expression, e.g. `adjustments[9] = 999.9`.
+
+ Causes all adjustment values in collection to be written to the XML.
"""
- self._adjustments_[key].effective_value = value
+ self._adjustments_[idx].effective_value = value
self._rewrite_guides()
- def _initialized_adjustments(self, prstGeom):
- """
- Return an initialized list of adjustment values based on the contents
- of *prstGeom*
- """
+ def _initialized_adjustments(self, prstGeom: CT_PresetGeometry2D | None) -> list[Adjustment]:
+ """Return an initialized list of adjustment values based on the contents of `prstGeom`."""
if prstGeom is None:
return []
davs = AutoShapeType.default_adjustment_values(prstGeom.prst)
@@ -127,19 +120,21 @@ def _initialized_adjustments(self, prstGeom):
return adjustments
def _rewrite_guides(self):
- """
- Write ```` elements to the XML, one for each adjustment value.
+ """Write `a:gd` elements to the XML, one for each adjustment value.
+
Any existing guide elements are overwritten.
"""
guides = [(adj.name, adj.val) for adj in self._adjustments_]
self._prstGeom.rewrite_guides(guides)
@staticmethod
- def _update_adjustments_with_actuals(adjustments, guides):
- """
- Update |Adjustment| instances in *adjustments* with actual values
- held in *guides*, a list of ```` elements. Guides with a name
- that does not match an adjustment object are skipped.
+ def _update_adjustments_with_actuals(
+ adjustments: Iterable[Adjustment], guides: Iterable[CT_GeomGuide]
+ ):
+ """Update |Adjustment| instances in `adjustments` with actual values held in `guides`.
+
+ `guides` is a list of `a:gd` elements. Guides with a name that does not match an adjustment
+ object are skipped.
"""
adjustments_by_name = dict((adj.name, adj) for adj in adjustments)
for gd in guides:
@@ -153,11 +148,8 @@ def _update_adjustments_with_actuals(adjustments, guides):
return
@property
- def _adjustments(self):
- """
- Sequence containing direct references to the |Adjustment| objects
- contained in collection.
- """
+ def _adjustments(self) -> tuple[Adjustment, ...]:
+ """Sequence of |Adjustment| objects contained in collection."""
return tuple(self._adjustments_)
def __len__(self):
@@ -165,103 +157,88 @@ def __len__(self):
return len(self._adjustments_)
-class AutoShapeType(object):
- """
- Return an instance of |AutoShapeType| containing metadata for an auto
- shape of type identified by *autoshape_type_id*. Instances are cached, so
- no more than one instance for a particular auto shape type is in memory.
+class AutoShapeType:
+ """Provides access to metadata for an auto-shape of type identified by `autoshape_type_id`.
+
+ Instances are cached, so no more than one instance for a particular auto shape type is in
+ memory.
Instances provide the following attributes:
.. attribute:: autoshape_type_id
Integer uniquely identifying this auto shape type. Corresponds to a
- value in ``pptx.constants.MSO`` like ``MSO_SHAPE.ROUNDED_RECTANGLE``.
+ value in `pptx.constants.MSO` like `MSO_SHAPE.ROUNDED_RECTANGLE`.
.. attribute:: basename
- Base part of shape name for auto shapes of this type, e.g. ``Rounded
- Rectangle`` becomes ``Rounded Rectangle 99`` when the distinguishing
+ Base part of shape name for auto shapes of this type, e.g. `Rounded
+ Rectangle` becomes `Rounded Rectangle 99` when the distinguishing
integer is added to the shape name.
.. attribute:: prst
- String identifier for this auto shape type used in the ````
+ String identifier for this auto shape type used in the `a:prstGeom`
element.
- .. attribute:: desc
-
- Informal string description of auto shape.
-
"""
- _instances = {}
+ _instances: dict[MSO_AUTO_SHAPE_TYPE, AutoShapeType] = {}
- def __new__(cls, autoshape_type_id):
- """
- Only create new instance on first call for content_type. After that,
- use cached instance.
+ def __new__(cls, autoshape_type_id: MSO_AUTO_SHAPE_TYPE) -> AutoShapeType:
+ """Only create new instance on first call for content_type.
+
+ After that, use cached instance.
"""
- # if there's not a matching instance in the cache, create one
+ # -- if there's not a matching instance in the cache, create one --
if autoshape_type_id not in cls._instances:
inst = super(AutoShapeType, cls).__new__(cls)
cls._instances[autoshape_type_id] = inst
- # return the instance; note that __init__() gets called either way
+ # -- return the instance; note that __init__() gets called either way --
return cls._instances[autoshape_type_id]
- def __init__(self, autoshape_type_id):
- """Initialize attributes from constant values in pptx.spec"""
- # skip loading if this instance is from the cache
+ def __init__(self, autoshape_type_id: MSO_AUTO_SHAPE_TYPE):
+ """Initialize attributes from constant values in `pptx.spec`."""
+ # -- skip loading if this instance is from the cache --
if hasattr(self, "_loaded"):
return
- # raise on bad autoshape_type_id
+ # -- raise on bad autoshape_type_id --
if autoshape_type_id not in autoshape_types:
raise KeyError(
- "no autoshape type with id '%s' in pptx.spec.autoshape_types"
- % autoshape_type_id
+ "no autoshape type with id '%s' in pptx.spec.autoshape_types" % autoshape_type_id
)
- # otherwise initialize new instance
+ # -- otherwise initialize new instance --
autoshape_type = autoshape_types[autoshape_type_id]
self._autoshape_type_id = autoshape_type_id
self._basename = autoshape_type["basename"]
self._loaded = True
@property
- def autoshape_type_id(self):
- """
- MSO_AUTO_SHAPE_TYPE enumeration value for this auto shape type
- """
+ def autoshape_type_id(self) -> MSO_AUTO_SHAPE_TYPE:
+ """MSO_AUTO_SHAPE_TYPE enumeration member identifying this auto shape type."""
return self._autoshape_type_id
@property
- def basename(self):
+ def basename(self) -> str:
"""Base of shape name for this auto shape type.
- A shape name is like "Rounded Rectangle 7" and appears as an XML attribute for
- example at `p:sp/p:nvSpPr/p:cNvPr{name}`. This basename value is the name less
- the distinguishing integer. This value is escaped because at least one
- autoshape-type name includes double quotes ('"No" Symbol').
+ A shape name is like "Rounded Rectangle 7" and appears as an XML attribute for example at
+ `p:sp/p:nvSpPr/p:cNvPr{name}`. This basename value is the name less the distinguishing
+ integer. This value is escaped because at least one autoshape-type name includes double
+ quotes ('"No" Symbol').
"""
return saxutils.escape(self._basename, {'"': """})
@classmethod
- def default_adjustment_values(cls, prst):
- """
- Return sequence of name, value tuples representing the adjustment
- value defaults for the auto shape type identified by *prst*.
- """
+ def default_adjustment_values(cls, prst: MSO_AUTO_SHAPE_TYPE) -> tuple[AdjustmentValue, ...]:
+ """Sequence of (name, value) pair adjustment value defaults for `prst` autoshape-type."""
return autoshape_types[prst]["avLst"]
- @property
- def desc(self):
- """Informal description of this auto shape type"""
- return self._desc
-
@classmethod
- def id_from_prst(cls, prst):
- """
- Return auto shape id (e.g. ``MSO_SHAPE.RECTANGLE``) corresponding to
- preset geometry keyword *prst*.
+ def id_from_prst(cls, prst: str) -> MSO_AUTO_SHAPE_TYPE:
+ """Select auto shape type with matching `prst`.
+
+ e.g. `MSO_SHAPE.RECTANGLE` corresponding to preset geometry keyword `"rect"`.
"""
return MSO_AUTO_SHAPE_TYPE.from_xml(prst)
@@ -269,8 +246,8 @@ def id_from_prst(cls, prst):
def prst(self):
"""
Preset geometry identifier string for this auto shape. Used in the
- ``prst`` attribute of ```` element to specify the geometry
- to be used in rendering the shape, for example ``'roundRect'``.
+ `prst` attribute of `a:prstGeom` element to specify the geometry
+ to be used in rendering the shape, for example `'roundRect'`.
"""
return MSO_AUTO_SHAPE_TYPE.to_xml(self._autoshape_type_id)
@@ -278,28 +255,24 @@ def prst(self):
class Shape(BaseShape):
"""A shape that can appear on a slide.
- Corresponds to the ```` element that can appear in any of the slide-type parts
+ Corresponds to the `p:sp` element that can appear in any of the slide-type parts
(slide, slideLayout, slideMaster, notesPage, notesMaster, handoutMaster).
"""
- def __init__(self, sp, parent):
+ def __init__(self, sp: CT_Shape, parent: ProvidesPart):
super(Shape, self).__init__(sp, parent)
self._sp = sp
@lazyproperty
- def adjustments(self):
- """
- Read-only reference to |AdjustmentCollection| instance for this
- shape
- """
+ def adjustments(self) -> AdjustmentCollection:
+ """Read-only reference to |AdjustmentCollection| instance for this shape."""
return AdjustmentCollection(self._sp.prstGeom)
@property
def auto_shape_type(self):
- """
- Enumeration value identifying the type of this auto shape, like
- ``MSO_SHAPE.ROUNDED_RECTANGLE``. Raises |ValueError| if this shape is
- not an auto shape.
+ """Enumeration value identifying the type of this auto shape.
+
+ Like `MSO_SHAPE.ROUNDED_RECTANGLE`. Raises |ValueError| if this shape is not an auto shape.
"""
if not self._sp.is_autoshape:
raise ValueError("shape is not an auto shape")
@@ -307,49 +280,40 @@ def auto_shape_type(self):
@lazyproperty
def fill(self):
- """
- |FillFormat| instance for this shape, providing access to fill
- properties such as fill color.
+ """|FillFormat| instance for this shape.
+
+ Provides access to fill properties such as fill color.
"""
return FillFormat.from_fill_parent(self._sp.spPr)
def get_or_add_ln(self):
- """
- Return the ```` element containing the line format properties
- XML for this shape.
- """
+ """Return the `a:ln` element containing the line format properties XML for this shape."""
return self._sp.get_or_add_ln()
@property
- def has_text_frame(self):
- """
- |True| if this shape can contain text. Always |True| for an
- AutoShape.
- """
+ def has_text_frame(self) -> bool:
+ """|True| if this shape can contain text. Always |True| for an AutoShape."""
return True
@lazyproperty
def line(self):
- """
- |LineFormat| instance for this shape, providing access to line
- properties such as line color.
+ """|LineFormat| instance for this shape.
+
+ Provides access to line properties such as line color.
"""
return LineFormat(self)
@property
def ln(self):
- """
- The ```` element containing the line format properties such as
- line color and width. |None| if no ```` element is present.
+ """The `a:ln` element containing the line format properties such as line color and width.
+
+ |None| if no `a:ln` element is present.
"""
return self._sp.ln
@property
- def shape_type(self):
- """
- Unique integer identifying the type of this shape, like
- ``MSO_SHAPE_TYPE.TEXT_BOX``.
- """
+ def shape_type(self) -> MSO_SHAPE_TYPE:
+ """Unique integer identifying the type of this shape, like `MSO_SHAPE_TYPE.TEXT_BOX`."""
if self.is_placeholder:
return MSO_SHAPE_TYPE.PLACEHOLDER
if self._sp.has_custom_geometry:
@@ -358,40 +322,34 @@ def shape_type(self):
return MSO_SHAPE_TYPE.AUTO_SHAPE
if self._sp.is_textbox:
return MSO_SHAPE_TYPE.TEXT_BOX
- msg = "Shape instance of unrecognized shape type"
- raise NotImplementedError(msg)
+ raise NotImplementedError("Shape instance of unrecognized shape type")
@property
- def text(self):
- """Read/write. Unicode (str in Python 3) representation of shape text.
-
- The returned string will contain a newline character (``"\\n"``) separating each
- paragraph and a vertical-tab (``"\\v"``) character for each line break (soft
- carriage return) in the shape's text.
-
- Assignment to *text* replaces all text previously contained in the shape, along
- with any paragraph or font formatting applied to it. A newline character
- (``"\\n"``) in the assigned text causes a new paragraph to be started.
- A vertical-tab (``"\\v"``) character in the assigned text causes a line-break
- (soft carriage-return) to be inserted. (The vertical-tab character appears in
- clipboard text copied from PowerPoint as its encoding of line-breaks.)
-
- Either bytes (Python 2 str) or unicode (Python 3 str) can be assigned. Bytes can
- be 7-bit ASCII or UTF-8 encoded 8-bit bytes. Bytes values are converted to
- unicode assuming UTF-8 encoding (which also works for ASCII).
+ def text(self) -> str:
+ """Read/write. Text in shape as a single string.
+
+ The returned string will contain a newline character (`"\\n"`) separating each paragraph
+ and a vertical-tab (`"\\v"`) character for each line break (soft carriage return) in the
+ shape's text.
+
+ Assignment to `text` replaces any text previously contained in the shape, along with any
+ paragraph or font formatting applied to it. A newline character (`"\\n"`) in the assigned
+ text causes a new paragraph to be started. A vertical-tab (`"\\v"`) character in the
+ assigned text causes a line-break (soft carriage-return) to be inserted. (The vertical-tab
+ character appears in clipboard text copied from PowerPoint as its str encoding of
+ line-breaks.)
"""
return self.text_frame.text
@text.setter
- def text(self, text):
+ def text(self, text: str):
self.text_frame.text = text
@property
def text_frame(self):
"""|TextFrame| instance for this shape.
- Contains the text of the shape and provides access to text formatting
- properties.
+ Contains the text of the shape and provides access to text formatting properties.
"""
- txBody = self._element.get_or_add_txBody()
+ txBody = self._sp.get_or_add_txBody()
return TextFrame(txBody, self)
diff --git a/src/pptx/shapes/base.py b/src/pptx/shapes/base.py
index c9472434d..751235023 100644
--- a/src/pptx/shapes/base.py
+++ b/src/pptx/shapes/base.py
@@ -1,14 +1,22 @@
-# encoding: utf-8
-
"""Base shape-related objects such as BaseShape."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, cast
from pptx.action import ActionSetting
from pptx.dml.effect import ShadowFormat
from pptx.shared import ElementProxy
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.enum.shapes import MSO_SHAPE_TYPE, PP_PLACEHOLDER
+ from pptx.oxml.shapes import ShapeElement
+ from pptx.oxml.shapes.shared import CT_Placeholder
+ from pptx.parts.slide import BaseSlidePart
+ from pptx.types import ProvidesPart
+ from pptx.util import Length
+
class BaseShape(object):
"""Base class for shape objects.
@@ -16,158 +24,148 @@ class BaseShape(object):
Subclasses include |Shape|, |Picture|, and |GraphicFrame|.
"""
- def __init__(self, shape_elm, parent):
- super(BaseShape, self).__init__()
+ def __init__(self, shape_elm: ShapeElement, parent: ProvidesPart):
+ super().__init__()
self._element = shape_elm
self._parent = parent
- def __eq__(self, other):
+ def __eq__(self, other: object) -> bool:
"""|True| if this shape object proxies the same element as *other*.
- Equality for proxy objects is defined as referring to the same XML
- element, whether or not they are the same proxy object instance.
+ Equality for proxy objects is defined as referring to the same XML element, whether or not
+ they are the same proxy object instance.
"""
if not isinstance(other, BaseShape):
return False
return self._element is other._element
- def __ne__(self, other):
+ def __ne__(self, other: object) -> bool:
if not isinstance(other, BaseShape):
return True
return self._element is not other._element
@lazyproperty
- def click_action(self):
+ def click_action(self) -> ActionSetting:
"""|ActionSetting| instance providing access to click behaviors.
- Click behaviors are hyperlink-like behaviors including jumping to
- a hyperlink (web page) or to another slide in the presentation. The
- click action is that defined on the overall shape, not a run of text
- within the shape. An |ActionSetting| object is always returned, even
- when no click behavior is defined on the shape.
+ Click behaviors are hyperlink-like behaviors including jumping to a hyperlink (web page)
+ or to another slide in the presentation. The click action is that defined on the overall
+ shape, not a run of text within the shape. An |ActionSetting| object is always returned,
+ even when no click behavior is defined on the shape.
"""
- cNvPr = self._element._nvXxPr.cNvPr
+ cNvPr = self._element._nvXxPr.cNvPr # pyright: ignore[reportPrivateUsage]
return ActionSetting(cNvPr, self)
@property
- def element(self):
+ def element(self) -> ShapeElement:
"""`lxml` element for this shape, e.g. a CT_Shape instance.
- Note that manipulating this element improperly can produce an invalid
- presentation file. Make sure you know what you're doing if you use
- this to change the underlying XML.
+ Note that manipulating this element improperly can produce an invalid presentation file.
+ Make sure you know what you're doing if you use this to change the underlying XML.
"""
return self._element
@property
- def has_chart(self):
- """
- |True| if this shape is a graphic frame containing a chart object.
- |False| otherwise. When |True|, the chart object can be accessed
- using the ``.chart`` property.
+ def has_chart(self) -> bool:
+ """|True| if this shape is a graphic frame containing a chart object.
+
+ |False| otherwise. When |True|, the chart object can be accessed using the ``.chart``
+ property.
"""
# This implementation is unconditionally False, the True version is
# on GraphicFrame subclass.
return False
@property
- def has_table(self):
- """
- |True| if this shape is a graphic frame containing a table object.
- |False| otherwise. When |True|, the table object can be accessed
- using the ``.table`` property.
+ def has_table(self) -> bool:
+ """|True| if this shape is a graphic frame containing a table object.
+
+ |False| otherwise. When |True|, the table object can be accessed using the ``.table``
+ property.
"""
# This implementation is unconditionally False, the True version is
# on GraphicFrame subclass.
return False
@property
- def has_text_frame(self):
- """
- |True| if this shape can contain text.
- """
+ def has_text_frame(self) -> bool:
+ """|True| if this shape can contain text."""
# overridden on Shape to return True. Only has text frame
return False
@property
- def height(self):
- """
- Read/write. Integer distance between top and bottom extents of shape
- in EMUs
- """
+ def height(self) -> Length:
+ """Read/write. Integer distance between top and bottom extents of shape in EMUs."""
return self._element.cy
@height.setter
- def height(self, value):
+ def height(self, value: Length):
self._element.cy = value
@property
- def is_placeholder(self):
- """
- True if this shape is a placeholder. A shape is a placeholder if it
- has a element.
+ def is_placeholder(self) -> bool:
+ """True if this shape is a placeholder.
+
+ A shape is a placeholder if it has a element.
"""
return self._element.has_ph_elm
@property
- def left(self):
- """
- Read/write. Integer distance of the left edge of this shape from the
- left edge of the slide, in English Metric Units (EMU)
+ def left(self) -> Length:
+ """Integer distance of the left edge of this shape from the left edge of the slide.
+
+ Read/write. Expressed in English Metric Units (EMU)
"""
return self._element.x
@left.setter
- def left(self, value):
+ def left(self, value: Length):
self._element.x = value
@property
- def name(self):
- """
- Name of this shape, e.g. 'Picture 7'
- """
+ def name(self) -> str:
+ """Name of this shape, e.g. 'Picture 7'."""
return self._element.shape_name
@name.setter
- def name(self, value):
- self._element._nvXxPr.cNvPr.name = value
+ def name(self, value: str):
+ self._element._nvXxPr.cNvPr.name = value # pyright: ignore[reportPrivateUsage]
@property
- def part(self):
+ def part(self) -> BaseSlidePart:
"""The package part containing this shape.
- A |BaseSlidePart| subclass in this case. Access to a slide part
- should only be required if you are extending the behavior of |pp| API
- objects.
+ A |BaseSlidePart| subclass in this case. Access to a slide part should only be required if
+ you are extending the behavior of |pp| API objects.
"""
- return self._parent.part
+ return cast("BaseSlidePart", self._parent.part)
@property
- def placeholder_format(self):
- """
- A |_PlaceholderFormat| object providing access to
- placeholder-specific properties such as placeholder type. Raises
- |ValueError| on access if the shape is not a placeholder.
+ def placeholder_format(self) -> _PlaceholderFormat:
+ """Provides access to placeholder-specific properties such as placeholder type.
+
+ Raises |ValueError| on access if the shape is not a placeholder.
"""
- if not self.is_placeholder:
+ ph = self._element.ph
+ if ph is None:
raise ValueError("shape is not a placeholder")
- return _PlaceholderFormat(self._element.ph)
+ return _PlaceholderFormat(ph)
@property
- def rotation(self):
- """
- Read/write float. Degrees of clockwise rotation. Negative values can
- be assigned to indicate counter-clockwise rotation, e.g. assigning
- -45.0 will change setting to 315.0.
+ def rotation(self) -> float:
+ """Degrees of clockwise rotation.
+
+ Read/write float. Negative values can be assigned to indicate counter-clockwise rotation,
+ e.g. assigning -45.0 will change setting to 315.0.
"""
return self._element.rot
@rotation.setter
- def rotation(self, value):
+ def rotation(self, value: float):
self._element.rot = value
@lazyproperty
- def shadow(self):
+ def shadow(self) -> ShadowFormat:
"""|ShadowFormat| object providing access to shadow for this shape.
A |ShadowFormat| object is always returned, even when no shadow is
@@ -177,7 +175,7 @@ def shadow(self):
return ShadowFormat(self._element.spPr)
@property
- def shape_id(self):
+ def shape_id(self) -> int:
"""Read-only positive integer identifying this shape.
The id of a shape is unique among all shapes on a slide.
@@ -185,68 +183,62 @@ def shape_id(self):
return self._element.shape_id
@property
- def shape_type(self):
- """
- Unique integer identifying the type of this shape, like
- ``MSO_SHAPE_TYPE.CHART``. Must be implemented by subclasses.
+ def shape_type(self) -> MSO_SHAPE_TYPE:
+ """A member of MSO_SHAPE_TYPE classifying this shape by type.
+
+ Like ``MSO_SHAPE_TYPE.CHART``. Must be implemented by subclasses.
"""
- # # This one returns |None| unconditionally to account for shapes
- # # that haven't been implemented yet, like group shape and chart.
- # # Once those are done this should raise |NotImplementedError|.
- # msg = 'shape_type property must be implemented by subclasses'
- # raise NotImplementedError(msg)
- return None
+ raise NotImplementedError(f"{type(self).__name__} does not implement `.shape_type`")
@property
- def top(self):
- """
- Read/write. Integer distance of the top edge of this shape from the
- top edge of the slide, in English Metric Units (EMU)
+ def top(self) -> Length:
+ """Distance from the top edge of the slide to the top edge of this shape.
+
+ Read/write. Expressed in English Metric Units (EMU)
"""
return self._element.y
@top.setter
- def top(self, value):
+ def top(self, value: Length):
self._element.y = value
@property
- def width(self):
- """
- Read/write. Integer distance between left and right extents of shape
- in EMUs
+ def width(self) -> Length:
+ """Distance between left and right extents of this shape.
+
+ Read/write. Expressed in English Metric Units (EMU).
"""
return self._element.cx
@width.setter
- def width(self, value):
+ def width(self, value: Length):
self._element.cx = value
class _PlaceholderFormat(ElementProxy):
+ """Provides properties specific to placeholders, such as the placeholder type.
+
+ Accessed via the :attr:`~.BaseShape.placeholder_format` property of a placeholder shape,
"""
- Accessed via the :attr:`~.BaseShape.placeholder_format` property of
- a placeholder shape, provides properties specific to placeholders, such
- as the placeholder type.
- """
+
+ def __init__(self, element: CT_Placeholder):
+ super().__init__(element)
+ self._ph = element
@property
- def element(self):
- """
- The `p:ph` element proxied by this object.
- """
- return super(_PlaceholderFormat, self).element
+ def element(self) -> CT_Placeholder:
+ """The `p:ph` element proxied by this object."""
+ return self._ph
@property
- def idx(self):
- """
- Integer placeholder 'idx' attribute.
- """
- return self._element.idx
+ def idx(self) -> int:
+ """Integer placeholder 'idx' attribute."""
+ return self._ph.idx
@property
- def type(self):
- """
- Placeholder type, a member of the :ref:`PpPlaceholderType`
- enumeration, e.g. PP_PLACEHOLDER.CHART
+ def type(self) -> PP_PLACEHOLDER:
+ """Placeholder type.
+
+ A member of the :ref:`PpPlaceholderType` enumeration, e.g. PP_PLACEHOLDER.CHART
"""
- return self._element.type
+ return self._ph.type
diff --git a/src/pptx/shapes/connector.py b/src/pptx/shapes/connector.py
index ecd8ec9a9..070b080d5 100644
--- a/src/pptx/shapes/connector.py
+++ b/src/pptx/shapes/connector.py
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
"""Connector (line) shape and related objects.
A connector is a line shape having end-points that can be connected to other
@@ -7,7 +5,7 @@
elbows, or can be curved.
"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
from pptx.dml.line import LineFormat
from pptx.enum.shapes import MSO_SHAPE_TYPE
diff --git a/src/pptx/shapes/freeform.py b/src/pptx/shapes/freeform.py
index 0168b2baf..e05b3484f 100644
--- a/src/pptx/shapes/freeform.py
+++ b/src/pptx/shapes/freeform.py
@@ -1,28 +1,50 @@
-# encoding: utf-8
-
"""Objects related to construction of freeform shapes."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from collections.abc import Sequence
+from typing import TYPE_CHECKING, Iterable, Iterator
+
+from pptx.util import Emu, lazyproperty
+
+if TYPE_CHECKING:
+ from typing_extensions import TypeAlias
+
+ from pptx.oxml.shapes.autoshape import (
+ CT_Path2D,
+ CT_Path2DClose,
+ CT_Path2DLineTo,
+ CT_Path2DMoveTo,
+ CT_Shape,
+ )
+ from pptx.shapes.shapetree import _BaseGroupShapes # pyright: ignore[reportPrivateUsage]
+ from pptx.util import Length
-from pptx.compat import Sequence
-from pptx.util import lazyproperty
+CT_DrawingOperation: TypeAlias = "CT_Path2DClose | CT_Path2DLineTo | CT_Path2DMoveTo"
+DrawingOperation: TypeAlias = "_LineSegment | _MoveTo | _Close"
-class FreeformBuilder(Sequence):
+class FreeformBuilder(Sequence[DrawingOperation]):
"""Allows a freeform shape to be specified and created.
- The initial pen position is provided on construction. From there, drawing
- proceeds using successive calls to draw line segments. The freeform shape
- may be closed by calling the :meth:`close` method.
+ The initial pen position is provided on construction. From there, drawing proceeds using
+ successive calls to draw line segments. The freeform shape may be closed by calling the
+ :meth:`close` method.
- A shape may have more than one contour, in which case overlapping areas
- are "subtracted". A contour is a sequence of line segments beginning with
- a "move-to" operation. A move-to operation is automatically inserted in
- each new freeform; additional move-to ops can be inserted with the
- `.move_to()` method.
+ A shape may have more than one contour, in which case overlapping areas are "subtracted". A
+ contour is a sequence of line segments beginning with a "move-to" operation. A move-to
+ operation is automatically inserted in each new freeform; additional move-to ops can be
+ inserted with the `.move_to()` method.
"""
- def __init__(self, shapes, start_x, start_y, x_scale, y_scale):
+ def __init__(
+ self,
+ shapes: _BaseGroupShapes,
+ start_x: Length,
+ start_y: Length,
+ x_scale: float,
+ y_scale: float,
+ ):
super(FreeformBuilder, self).__init__()
self._shapes = shapes
self._start_x = start_x
@@ -30,34 +52,41 @@ def __init__(self, shapes, start_x, start_y, x_scale, y_scale):
self._x_scale = x_scale
self._y_scale = y_scale
- def __getitem__(self, idx):
+ def __getitem__( # pyright: ignore[reportIncompatibleMethodOverride]
+ self, idx: int
+ ) -> DrawingOperation:
return self._drawing_operations.__getitem__(idx)
- def __iter__(self):
+ def __iter__(self) -> Iterator[DrawingOperation]:
return self._drawing_operations.__iter__()
def __len__(self):
return self._drawing_operations.__len__()
@classmethod
- def new(cls, shapes, start_x, start_y, x_scale, y_scale):
+ def new(
+ cls,
+ shapes: _BaseGroupShapes,
+ start_x: float,
+ start_y: float,
+ x_scale: float,
+ y_scale: float,
+ ):
"""Return a new |FreeformBuilder| object.
The initial pen location is specified (in local coordinates) by
- (*start_x*, *start_y*).
+ (`start_x`, `start_y`).
"""
- return cls(shapes, int(round(start_x)), int(round(start_y)), x_scale, y_scale)
+ return cls(shapes, Emu(int(round(start_x))), Emu(int(round(start_y))), x_scale, y_scale)
- def add_line_segments(self, vertices, close=True):
- """Add a straight line segment to each point in *vertices*.
+ def add_line_segments(self, vertices: Iterable[tuple[float, float]], close: bool = True):
+ """Add a straight line segment to each point in `vertices`.
- *vertices* must be an iterable of (x, y) pairs (2-tuples). Each x and
- y value is rounded to the nearest integer before use. The optional
- *close* parameter determines whether the resulting contour is
- *closed* or left *open*.
+ `vertices` must be an iterable of (x, y) pairs (2-tuples). Each x and y value is rounded
+ to the nearest integer before use. The optional `close` parameter determines whether the
+ resulting contour is `closed` or left `open`.
- Returns this |FreeformBuilder| object so it can be used in chained
- calls.
+ Returns this |FreeformBuilder| object so it can be used in chained calls.
"""
for x, y in vertices:
self._add_line_segment(x, y)
@@ -65,109 +94,109 @@ def add_line_segments(self, vertices, close=True):
self._add_close()
return self
- def convert_to_shape(self, origin_x=0, origin_y=0):
+ def convert_to_shape(self, origin_x: Length = Emu(0), origin_y: Length = Emu(0)):
"""Return new freeform shape positioned relative to specified offset.
- *origin_x* and *origin_y* locate the origin of the local coordinate
- system in slide coordinates (EMU), perhaps most conveniently by use
- of a |Length| object.
+ `origin_x` and `origin_y` locate the origin of the local coordinate system in slide
+ coordinates (EMU), perhaps most conveniently by use of a |Length| object.
- Note that this method may be called more than once to add multiple
- shapes of the same geometry in different locations on the slide.
+ Note that this method may be called more than once to add multiple shapes of the same
+ geometry in different locations on the slide.
"""
sp = self._add_freeform_sp(origin_x, origin_y)
path = self._start_path(sp)
for drawing_operation in self:
drawing_operation.apply_operation_to(path)
- return self._shapes._shape_factory(sp)
+ return self._shapes._shape_factory(sp) # pyright: ignore[reportPrivateUsage]
- def move_to(self, x, y):
+ def move_to(self, x: float, y: float):
"""Move pen to (x, y) (local coordinates) without drawing line.
- Returns this |FreeformBuilder| object so it can be used in chained
- calls.
+ Returns this |FreeformBuilder| object so it can be used in chained calls.
"""
self._drawing_operations.append(_MoveTo.new(self, x, y))
return self
@property
- def shape_offset_x(self):
+ def shape_offset_x(self) -> Length:
"""Return x distance of shape origin from local coordinate origin.
- The returned integer represents the leftmost extent of the freeform
- shape, in local coordinates. Note that the bounding box of the shape
- need not start at the local origin.
+ The returned integer represents the leftmost extent of the freeform shape, in local
+ coordinates. Note that the bounding box of the shape need not start at the local origin.
"""
min_x = self._start_x
for drawing_operation in self:
- if hasattr(drawing_operation, "x"):
- min_x = min(min_x, drawing_operation.x)
- return min_x
+ if isinstance(drawing_operation, _Close):
+ continue
+ min_x = min(min_x, drawing_operation.x)
+ return Emu(min_x)
@property
- def shape_offset_y(self):
+ def shape_offset_y(self) -> Length:
"""Return y distance of shape origin from local coordinate origin.
- The returned integer represents the topmost extent of the freeform
- shape, in local coordinates. Note that the bounding box of the shape
- need not start at the local origin.
+ The returned integer represents the topmost extent of the freeform shape, in local
+ coordinates. Note that the bounding box of the shape need not start at the local origin.
"""
min_y = self._start_y
for drawing_operation in self:
- if hasattr(drawing_operation, "y"):
- min_y = min(min_y, drawing_operation.y)
- return min_y
+ if isinstance(drawing_operation, _Close):
+ continue
+ min_y = min(min_y, drawing_operation.y)
+ return Emu(min_y)
def _add_close(self):
"""Add a close |_Close| operation to the drawing sequence."""
self._drawing_operations.append(_Close.new())
- def _add_freeform_sp(self, origin_x, origin_y):
+ def _add_freeform_sp(self, origin_x: Length, origin_y: Length):
"""Add a freeform `p:sp` element having no drawing elements.
- *origin_x* and *origin_y* are specified in slide coordinates, and
- represent the location of the local coordinates origin on the slide.
+ `origin_x` and `origin_y` are specified in slide coordinates, and represent the location
+ of the local coordinates origin on the slide.
"""
- spTree = self._shapes._spTree
+ spTree = self._shapes._spTree # pyright: ignore[reportPrivateUsage]
return spTree.add_freeform_sp(
origin_x + self._left, origin_y + self._top, self._width, self._height
)
- def _add_line_segment(self, x, y):
+ def _add_line_segment(self, x: float, y: float) -> None:
"""Add a |_LineSegment| operation to the drawing sequence."""
self._drawing_operations.append(_LineSegment.new(self, x, y))
@lazyproperty
- def _drawing_operations(self):
+ def _drawing_operations(self) -> list[DrawingOperation]:
"""Return the sequence of drawing operation objects for freeform."""
return []
@property
- def _dx(self):
- """Return integer width of this shape's path in local units."""
+ def _dx(self) -> Length:
+ """Return width of this shape's path in local units."""
min_x = max_x = self._start_x
for drawing_operation in self:
- if hasattr(drawing_operation, "x"):
- min_x = min(min_x, drawing_operation.x)
- max_x = max(max_x, drawing_operation.x)
- return max_x - min_x
+ if isinstance(drawing_operation, _Close):
+ continue
+ min_x = min(min_x, drawing_operation.x)
+ max_x = max(max_x, drawing_operation.x)
+ return Emu(max_x - min_x)
@property
- def _dy(self):
+ def _dy(self) -> Length:
"""Return integer height of this shape's path in local units."""
min_y = max_y = self._start_y
for drawing_operation in self:
- if hasattr(drawing_operation, "y"):
- min_y = min(min_y, drawing_operation.y)
- max_y = max(max_y, drawing_operation.y)
- return max_y - min_y
+ if isinstance(drawing_operation, _Close):
+ continue
+ min_y = min(min_y, drawing_operation.y)
+ max_y = max(max_y, drawing_operation.y)
+ return Emu(max_y - min_y)
@property
def _height(self):
"""Return vertical size of this shape's path in slide coordinates.
- This value is based on the actual extents of the shape and does not
- include any positioning offset.
+ This value is based on the actual extents of the shape and does not include any
+ positioning offset.
"""
return int(round(self._dy * self._y_scale))
@@ -175,26 +204,25 @@ def _height(self):
def _left(self):
"""Return leftmost extent of this shape's path in slide coordinates.
- Note that this value does not include any positioning offset; it
- assumes the drawing (local) coordinate origin is at (0, 0) on the
- slide.
+ Note that this value does not include any positioning offset; it assumes the drawing
+ (local) coordinate origin is at (0, 0) on the slide.
"""
return int(round(self.shape_offset_x * self._x_scale))
- def _local_to_shape(self, local_x, local_y):
+ def _local_to_shape(self, local_x: Length, local_y: Length) -> tuple[Length, Length]:
"""Translate local coordinates point to shape coordinates.
- Shape coordinates have the same unit as local coordinates, but are
- offset such that the origin of the shape coordinate system (0, 0) is
- located at the top-left corner of the shape bounding box.
+ Shape coordinates have the same unit as local coordinates, but are offset such that the
+ origin of the shape coordinate system (0, 0) is located at the top-left corner of the
+ shape bounding box.
"""
- return (local_x - self.shape_offset_x, local_y - self.shape_offset_y)
+ return Emu(local_x - self.shape_offset_x), Emu(local_y - self.shape_offset_y)
- def _start_path(self, sp):
- """Return a newly created `a:path` element added to *sp*.
+ def _start_path(self, sp: CT_Shape) -> CT_Path2D:
+ """Return a newly created `a:path` element added to `sp`.
- The returned `a:path` element has an `a:moveTo` element representing
- the shape starting point as its only child.
+ The returned `a:path` element has an `a:moveTo` element representing the shape starting
+ point as its only child.
"""
path = sp.add_path(w=self._dx, h=self._dy)
path.add_moveTo(*self._local_to_shape(self._start_x, self._start_y))
@@ -204,9 +232,9 @@ def _start_path(self, sp):
def _top(self):
"""Return topmost extent of this shape's path in slide coordinates.
- Note that this value does not include any positioning offset; it
- assumes the drawing (local) coordinate origin is located at slide
- coordinates (0, 0) (top-left corner of slide).
+ Note that this value does not include any positioning offset; it assumes the drawing
+ (local) coordinate origin is located at slide coordinates (0, 0) (top-left corner of
+ slide).
"""
return int(round(self.shape_offset_y * self._y_scale))
@@ -214,8 +242,8 @@ def _top(self):
def _width(self):
"""Return width of this shape's path in slide coordinates.
- This value is based on the actual extents of the shape path and does
- not include any positioning offset.
+ This value is based on the actual extents of the shape path and does not include any
+ positioning offset.
"""
return int(round(self._dx * self._x_scale))
@@ -223,25 +251,24 @@ def _width(self):
class _BaseDrawingOperation(object):
"""Base class for freeform drawing operations.
- A drawing operation has at least one location (x, y) in local
- coordinates.
+ A drawing operation has at least one location (x, y) in local coordinates.
"""
- def __init__(self, freeform_builder, x, y):
+ def __init__(self, freeform_builder: FreeformBuilder, x: Length, y: Length):
super(_BaseDrawingOperation, self).__init__()
self._freeform_builder = freeform_builder
self._x = x
self._y = y
- def apply_operation_to(self, path):
- """Add the XML element(s) implementing this operation to *path*.
+ def apply_operation_to(self, path: CT_Path2D) -> CT_DrawingOperation:
+ """Add the XML element(s) implementing this operation to `path`.
Must be implemented by each subclass.
"""
raise NotImplementedError("must be implemented by each subclass")
@property
- def x(self):
+ def x(self) -> Length:
"""Return the horizontal (x) target location of this operation.
The returned value is an integer in local coordinates.
@@ -249,7 +276,7 @@ def x(self):
return self._x
@property
- def y(self):
+ def y(self) -> Length:
"""Return the vertical (y) target location of this operation.
The returned value is an integer in local coordinates.
@@ -261,12 +288,12 @@ class _Close(object):
"""Specifies adding a `` element to the current contour."""
@classmethod
- def new(cls):
+ def new(cls) -> _Close:
"""Return a new _Close object."""
return cls()
- def apply_operation_to(self, path):
- """Add `a:close` element to *path*."""
+ def apply_operation_to(self, path: CT_Path2D) -> CT_Path2DClose:
+ """Add `a:close` element to `path`."""
return path.add_close()
@@ -274,21 +301,21 @@ class _LineSegment(_BaseDrawingOperation):
"""Specifies a straight line segment ending at the specified point."""
@classmethod
- def new(cls, freeform_builder, x, y):
+ def new(cls, freeform_builder: FreeformBuilder, x: float, y: float) -> _LineSegment:
"""Return a new _LineSegment object ending at point *(x, y)*.
- Both *x* and *y* are rounded to the nearest integer before use.
+ Both `x` and `y` are rounded to the nearest integer before use.
"""
- return cls(freeform_builder, int(round(x)), int(round(y)))
+ return cls(freeform_builder, Emu(int(round(x))), Emu(int(round(y))))
- def apply_operation_to(self, path):
- """Add `a:lnTo` element to *path* for this line segment.
+ def apply_operation_to(self, path: CT_Path2D) -> CT_Path2DLineTo:
+ """Add `a:lnTo` element to `path` for this line segment.
Returns the `a:lnTo` element newly added to the path.
"""
return path.add_lnTo(
- self._x - self._freeform_builder.shape_offset_x,
- self._y - self._freeform_builder.shape_offset_y,
+ Emu(self._x - self._freeform_builder.shape_offset_x),
+ Emu(self._y - self._freeform_builder.shape_offset_y),
)
@@ -296,16 +323,16 @@ class _MoveTo(_BaseDrawingOperation):
"""Specifies a new pen position."""
@classmethod
- def new(cls, freeform_builder, x, y):
- """Return a new _MoveTo object for move to point *(x, y)*.
+ def new(cls, freeform_builder: FreeformBuilder, x: float, y: float) -> _MoveTo:
+ """Return a new _MoveTo object for move to point `(x, y)`.
- Both *x* and *y* are rounded to the nearest integer before use.
+ Both `x` and `y` are rounded to the nearest integer before use.
"""
- return cls(freeform_builder, int(round(x)), int(round(y)))
+ return cls(freeform_builder, Emu(int(round(x))), Emu(int(round(y))))
- def apply_operation_to(self, path):
- """Add `a:moveTo` element to *path* for this line segment."""
+ def apply_operation_to(self, path: CT_Path2D) -> CT_Path2DMoveTo:
+ """Add `a:moveTo` element to `path` for this line segment."""
return path.add_moveTo(
- self._x - self._freeform_builder.shape_offset_x,
- self._y - self._freeform_builder.shape_offset_y,
+ Emu(self._x - self._freeform_builder.shape_offset_x),
+ Emu(self._y - self._freeform_builder.shape_offset_y),
)
diff --git a/src/pptx/shapes/graphfrm.py b/src/pptx/shapes/graphfrm.py
index de317cc5c..c0ed2bbab 100644
--- a/src/pptx/shapes/graphfrm.py
+++ b/src/pptx/shapes/graphfrm.py
@@ -1,11 +1,13 @@
-# encoding: utf-8
-
"""Graphic Frame shape and related objects.
A graphic frame is a common container for table, chart, smart art, and media
objects.
"""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, cast
+
from pptx.enum.shapes import MSO_SHAPE_TYPE
from pptx.shapes.base import BaseShape
from pptx.shared import ParentedElementProxy
@@ -15,16 +17,29 @@
GRAPHIC_DATA_URI_TABLE,
)
from pptx.table import Table
+from pptx.util import lazyproperty
+
+if TYPE_CHECKING:
+ from pptx.chart.chart import Chart
+ from pptx.dml.effect import ShadowFormat
+ from pptx.oxml.shapes.graphfrm import CT_GraphicalObjectData, CT_GraphicalObjectFrame
+ from pptx.parts.chart import ChartPart
+ from pptx.parts.slide import BaseSlidePart
+ from pptx.types import ProvidesPart
class GraphicFrame(BaseShape):
"""Container shape for table, chart, smart art, and media objects.
- Corresponds to a ```` element in the shape tree.
+ Corresponds to a `p:graphicFrame` element in the shape tree.
"""
+ def __init__(self, graphicFrame: CT_GraphicalObjectFrame, parent: ProvidesPart):
+ super().__init__(graphicFrame, parent)
+ self._graphicFrame = graphicFrame
+
@property
- def chart(self):
+ def chart(self) -> Chart:
"""The |Chart| object containing the chart in this graphic frame.
Raises |ValueError| if this graphic frame does not contain a chart.
@@ -34,61 +49,62 @@ def chart(self):
return self.chart_part.chart
@property
- def chart_part(self):
+ def chart_part(self) -> ChartPart:
"""The |ChartPart| object containing the chart in this graphic frame."""
- return self.part.related_part(self._element.chart_rId)
+ chart_rId = self._graphicFrame.chart_rId
+ if chart_rId is None:
+ raise ValueError("this graphic frame does not contain a chart")
+ return cast("ChartPart", self.part.related_part(chart_rId))
@property
- def has_chart(self):
+ def has_chart(self) -> bool:
"""|True| if this graphic frame contains a chart object. |False| otherwise.
- When |True|, the chart object can be accessed using the ``.chart`` property.
+ When |True|, the chart object can be accessed using the `.chart` property.
"""
- return self._element.graphicData_uri == GRAPHIC_DATA_URI_CHART
+ return self._graphicFrame.graphicData_uri == GRAPHIC_DATA_URI_CHART
@property
- def has_table(self):
+ def has_table(self) -> bool:
"""|True| if this graphic frame contains a table object, |False| otherwise.
When |True|, the table object can be accessed using the `.table` property.
"""
- return self._element.graphicData_uri == GRAPHIC_DATA_URI_TABLE
+ return self._graphicFrame.graphicData_uri == GRAPHIC_DATA_URI_TABLE
@property
- def ole_format(self):
- """Optional _OleFormat object for this graphic-frame shape.
+ def ole_format(self) -> _OleFormat:
+ """_OleFormat object for this graphic-frame shape.
- Raises `ValueError` on a GraphicFrame instance that does not contain an OLE
- object.
+ Raises `ValueError` on a GraphicFrame instance that does not contain an OLE object.
An shape that contains an OLE object will have `.shape_type` of either
`EMBEDDED_OLE_OBJECT` or `LINKED_OLE_OBJECT`.
"""
- if not self._element.has_oleobj:
+ if not self._graphicFrame.has_oleobj:
raise ValueError("not an OLE-object shape")
- return _OleFormat(self._element.graphicData, self._parent)
+ return _OleFormat(self._graphicFrame.graphicData, self._parent)
- @property
- def shadow(self):
+ @lazyproperty
+ def shadow(self) -> ShadowFormat:
"""Unconditionally raises |NotImplementedError|.
- Access to the shadow effect for graphic-frame objects is
- content-specific (i.e. different for charts, tables, etc.) and has
- not yet been implemented.
+ Access to the shadow effect for graphic-frame objects is content-specific (i.e. different
+ for charts, tables, etc.) and has not yet been implemented.
"""
raise NotImplementedError("shadow property on GraphicFrame not yet supported")
@property
- def shape_type(self):
+ def shape_type(self) -> MSO_SHAPE_TYPE:
"""Optional member of `MSO_SHAPE_TYPE` identifying the type of this shape.
- Possible values are ``MSO_SHAPE_TYPE.CHART``, ``MSO_SHAPE_TYPE.TABLE``,
- ``MSO_SHAPE_TYPE.EMBEDDED_OLE_OBJECT``, ``MSO_SHAPE_TYPE.LINKED_OLE_OBJECT``.
+ Possible values are `MSO_SHAPE_TYPE.CHART`, `MSO_SHAPE_TYPE.TABLE`,
+ `MSO_SHAPE_TYPE.EMBEDDED_OLE_OBJECT`, `MSO_SHAPE_TYPE.LINKED_OLE_OBJECT`.
- This value is `None` when none of these four types apply, for example when the
- shape contains SmartArt.
+ This value is `None` when none of these four types apply, for example when the shape
+ contains SmartArt.
"""
- graphicData_uri = self._element.graphicData_uri
+ graphicData_uri = self._graphicFrame.graphicData_uri
if graphicData_uri == GRAPHIC_DATA_URI_CHART:
return MSO_SHAPE_TYPE.CHART
elif graphicData_uri == GRAPHIC_DATA_URI_TABLE:
@@ -96,50 +112,55 @@ def shape_type(self):
elif graphicData_uri == GRAPHIC_DATA_URI_OLEOBJ:
return (
MSO_SHAPE_TYPE.EMBEDDED_OLE_OBJECT
- if self._element.is_embedded_ole_obj
+ if self._graphicFrame.is_embedded_ole_obj
else MSO_SHAPE_TYPE.LINKED_OLE_OBJECT
)
else:
- return None
+ return None # pyright: ignore[reportReturnType]
@property
- def table(self):
- """
- The |Table| object contained in this graphic frame. Raises
- |ValueError| if this graphic frame does not contain a table.
+ def table(self) -> Table:
+ """The |Table| object contained in this graphic frame.
+
+ Raises |ValueError| if this graphic frame does not contain a table.
"""
if not self.has_table:
raise ValueError("shape does not contain a table")
- tbl = self._element.graphic.graphicData.tbl
+ tbl = self._graphicFrame.graphic.graphicData.tbl
return Table(tbl, self)
class _OleFormat(ParentedElementProxy):
"""Provides attributes on an embedded OLE object."""
- def __init__(self, graphicData, parent):
- super(_OleFormat, self).__init__(graphicData, parent)
+ part: BaseSlidePart # pyright: ignore[reportIncompatibleMethodOverride]
+
+ def __init__(self, graphicData: CT_GraphicalObjectData, parent: ProvidesPart):
+ super().__init__(graphicData, parent)
self._graphicData = graphicData
@property
- def blob(self):
+ def blob(self) -> bytes | None:
"""Optional bytes of OLE object, suitable for loading or saving as a file.
- This value is None if the embedded object does not represent a "file".
+ This value is `None` if the embedded object does not represent a "file".
"""
- return self.part.related_part(self._graphicData.blob_rId).blob
+ blob_rId = self._graphicData.blob_rId
+ if blob_rId is None:
+ return None
+ return self.part.related_part(blob_rId).blob
@property
- def prog_id(self):
+ def prog_id(self) -> str | None:
"""str "progId" attribute of this embedded OLE object.
- The progId is a str like "Excel.Sheet.12" that identifies the "file-type" of the
- embedded object, or perhaps more precisely, the application (aka. "server" in
- OLE parlance) to be used to open this object.
+ The progId is a str like "Excel.Sheet.12" that identifies the "file-type" of the embedded
+ object, or perhaps more precisely, the application (aka. "server" in OLE parlance) to be
+ used to open this object.
"""
return self._graphicData.progId
@property
- def show_as_icon(self):
+ def show_as_icon(self) -> bool | None:
"""True when OLE object should appear as an icon (rather than preview)."""
return self._graphicData.showAsIcon
diff --git a/src/pptx/shapes/group.py b/src/pptx/shapes/group.py
index 0de06f853..717375851 100644
--- a/src/pptx/shapes/group.py
+++ b/src/pptx/shapes/group.py
@@ -1,20 +1,30 @@
-# encoding: utf-8
-
"""GroupShape and related objects."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
from pptx.dml.effect import ShadowFormat
from pptx.enum.shapes import MSO_SHAPE_TYPE
from pptx.shapes.base import BaseShape
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.action import ActionSetting
+ from pptx.oxml.shapes.groupshape import CT_GroupShape
+ from pptx.shapes.shapetree import GroupShapes
+ from pptx.types import ProvidesPart
+
class GroupShape(BaseShape):
"""A shape that acts as a container for other shapes."""
- @property
- def click_action(self):
+ def __init__(self, grpSp: CT_GroupShape, parent: ProvidesPart):
+ super().__init__(grpSp, parent)
+ self._grpSp = grpSp
+
+ @lazyproperty
+ def click_action(self) -> ActionSetting:
"""Unconditionally raises `TypeError`.
A group shape cannot have a click action or hover action.
@@ -22,27 +32,25 @@ def click_action(self):
raise TypeError("a group shape cannot have a click action")
@property
- def has_text_frame(self):
+ def has_text_frame(self) -> bool:
"""Unconditionally |False|.
- A group shape does not have a textframe and cannot itself contain
- text. This does not impact the ability of shapes contained by the
- group to each have their own text.
+ A group shape does not have a textframe and cannot itself contain text. This does not
+ impact the ability of shapes contained by the group to each have their own text.
"""
return False
@lazyproperty
- def shadow(self):
+ def shadow(self) -> ShadowFormat:
"""|ShadowFormat| object representing shadow effect for this group.
- A |ShadowFormat| object is always returned, even when no shadow is
- explicitly defined on this group shape (i.e. when the group inherits
- its shadow behavior).
+ A |ShadowFormat| object is always returned, even when no shadow is explicitly defined on
+ this group shape (i.e. when the group inherits its shadow behavior).
"""
- return ShadowFormat(self._element.grpSpPr)
+ return ShadowFormat(self._grpSp.grpSpPr)
@property
- def shape_type(self):
+ def shape_type(self) -> MSO_SHAPE_TYPE:
"""Member of :ref:`MsoShapeType` identifying the type of this shape.
Unconditionally `MSO_SHAPE_TYPE.GROUP` in this case
@@ -50,11 +58,11 @@ def shape_type(self):
return MSO_SHAPE_TYPE.GROUP
@lazyproperty
- def shapes(self):
+ def shapes(self) -> GroupShapes:
"""|GroupShapes| object for this group.
- The |GroupShapes| object provides access to the group's member shapes
- and provides methods for adding new ones.
+ The |GroupShapes| object provides access to the group's member shapes and provides methods
+ for adding new ones.
"""
from pptx.shapes.shapetree import GroupShapes
diff --git a/src/pptx/shapes/picture.py b/src/pptx/shapes/picture.py
index 1cb660e6f..59182860d 100644
--- a/src/pptx/shapes/picture.py
+++ b/src/pptx/shapes/picture.py
@@ -1,8 +1,8 @@
-# encoding: utf-8
-
"""Shapes based on the `p:pic` element, including Picture and Movie."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
from pptx.dml.line import LineFormat
from pptx.enum.shapes import MSO_SHAPE, MSO_SHAPE_TYPE, PP_MEDIA_TYPE
@@ -10,84 +10,87 @@
from pptx.shared import ParentedElementProxy
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.oxml.shapes.picture import CT_Picture
+ from pptx.oxml.shapes.shared import CT_LineProperties
+ from pptx.types import ProvidesPart
+
class _BasePicture(BaseShape):
"""Base class for shapes based on a `p:pic` element."""
- def __init__(self, pic, parent):
+ def __init__(self, pic: CT_Picture, parent: ProvidesPart):
super(_BasePicture, self).__init__(pic, parent)
self._pic = pic
@property
- def crop_bottom(self):
+ def crop_bottom(self) -> float:
"""|float| representing relative portion cropped from shape bottom.
- Read/write. 1.0 represents 100%. For example, 25% is represented by
- 0.25. Negative values are valid as are values greater than 1.0.
+ Read/write. 1.0 represents 100%. For example, 25% is represented by 0.25. Negative values
+ are valid as are values greater than 1.0.
"""
- return self._element.srcRect_b
+ return self._pic.srcRect_b
@crop_bottom.setter
- def crop_bottom(self, value):
- self._element.srcRect_b = value
+ def crop_bottom(self, value: float):
+ self._pic.srcRect_b = value
@property
- def crop_left(self):
+ def crop_left(self) -> float:
"""|float| representing relative portion cropped from left of shape.
- Read/write. 1.0 represents 100%. A negative value extends the side
- beyond the image boundary.
+ Read/write. 1.0 represents 100%. A negative value extends the side beyond the image
+ boundary.
"""
- return self._element.srcRect_l
+ return self._pic.srcRect_l
@crop_left.setter
- def crop_left(self, value):
- self._element.srcRect_l = value
+ def crop_left(self, value: float):
+ self._pic.srcRect_l = value
@property
- def crop_right(self):
+ def crop_right(self) -> float:
"""|float| representing relative portion cropped from right of shape.
Read/write. 1.0 represents 100%.
"""
- return self._element.srcRect_r
+ return self._pic.srcRect_r
@crop_right.setter
- def crop_right(self, value):
- self._element.srcRect_r = value
+ def crop_right(self, value: float):
+ self._pic.srcRect_r = value
@property
- def crop_top(self):
+ def crop_top(self) -> float:
"""|float| representing relative portion cropped from shape top.
Read/write. 1.0 represents 100%.
"""
- return self._element.srcRect_t
+ return self._pic.srcRect_t
@crop_top.setter
- def crop_top(self, value):
- self._element.srcRect_t = value
+ def crop_top(self, value: float):
+ self._pic.srcRect_t = value
def get_or_add_ln(self):
- """
- Return the `a:ln` element containing the line format properties XML
- for this `p:pic`-based shape.
+ """Return the `a:ln` element for this `p:pic`-based image.
+
+ The `a:ln` element contains the line format properties XML.
"""
return self._pic.get_or_add_ln()
@lazyproperty
- def line(self):
- """
- An instance of |LineFormat|, providing access to the properties of
- the outline bordering this shape, such as its color and width.
- """
+ def line(self) -> LineFormat:
+ """Provides access to properties of the picture outline, such as its color and width."""
return LineFormat(self)
@property
- def ln(self):
- """
- The ```` element containing the line format properties such as
- line color and width. |None| if no ```` element is present.
+ def ln(self) -> CT_LineProperties | None:
+ """The `a:ln` element for this `p:pic`.
+
+ Contains the line format properties such as line color and width. |None| if no `a:ln`
+ element is present.
"""
return self._pic.ln
@@ -95,26 +98,23 @@ def ln(self):
class Movie(_BasePicture):
"""A movie shape, one that places a video on a slide.
- Like |Picture|, a movie shape is based on the `p:pic` element. A movie is
- composed of a video and a *poster frame*, the placeholder image that
- represents the video before it is played.
+ Like |Picture|, a movie shape is based on the `p:pic` element. A movie is composed of a video
+ and a *poster frame*, the placeholder image that represents the video before it is played.
"""
@lazyproperty
- def media_format(self):
+ def media_format(self) -> _MediaFormat:
"""The |_MediaFormat| object for this movie.
- The |_MediaFormat| object provides access to formatting properties
- for the movie.
+ The |_MediaFormat| object provides access to formatting properties for the movie.
"""
- return _MediaFormat(self._element, self)
+ return _MediaFormat(self._pic, self)
@property
- def media_type(self):
+ def media_type(self) -> PP_MEDIA_TYPE:
"""Member of :ref:`PpMediaType` describing this shape.
- The return value is unconditionally `PP_MEDIA_TYPE.MOVIE` in this
- case.
+ The return value is unconditionally `PP_MEDIA_TYPE.MOVIE` in this case.
"""
return PP_MEDIA_TYPE.MOVIE
@@ -124,16 +124,16 @@ def poster_frame(self):
Returns |None| if this movie has no poster frame (uncommon).
"""
- slide_part, rId = self.part, self._element.blip_rId
+ slide_part, rId = self.part, self._pic.blip_rId
if rId is None:
return None
return slide_part.get_image(rId)
@property
- def shape_type(self):
+ def shape_type(self) -> MSO_SHAPE_TYPE:
"""Return member of :ref:`MsoShapeType` describing this shape.
- The return value is unconditionally ``MSO_SHAPE_TYPE.MEDIA`` in this
+ The return value is unconditionally `MSO_SHAPE_TYPE.MEDIA` in this
case.
"""
return MSO_SHAPE_TYPE.MEDIA
@@ -146,27 +146,22 @@ class Picture(_BasePicture):
"""
@property
- def auto_shape_type(self):
+ def auto_shape_type(self) -> MSO_SHAPE | None:
"""Member of MSO_SHAPE indicating masking shape.
- A picture can be masked by any of the so-called "auto-shapes"
- available in PowerPoint, such as an ellipse or triangle. When
- a picture is masked by a shape, the shape assumes the same dimensions
- as the picture and the portion of the picture outside the shape
- boundaries does not appear. Note the default value for
- a newly-inserted picture is `MSO_AUTO_SHAPE_TYPE.RECTANGLE`, which
- performs no cropping because the extents of the rectangle exactly
- correspond to the extents of the picture.
-
- The available shapes correspond to the members of
- :ref:`MsoAutoShapeType`.
-
- The return value can also be |None|, indicating the picture either
- has no geometry (not expected) or has custom geometry, like
- a freeform shape. A picture with no geometry will have no visible
- representation on the slide, although it can be selected. This is
- because without geometry, there is no "inside-the-shape" for it to
- appear in.
+ A picture can be masked by any of the so-called "auto-shapes" available in PowerPoint,
+ such as an ellipse or triangle. When a picture is masked by a shape, the shape assumes the
+ same dimensions as the picture and the portion of the picture outside the shape boundaries
+ does not appear. Note the default value for a newly-inserted picture is
+ `MSO_AUTO_SHAPE_TYPE.RECTANGLE`, which performs no cropping because the extents of the
+ rectangle exactly correspond to the extents of the picture.
+
+ The available shapes correspond to the members of :ref:`MsoAutoShapeType`.
+
+ The return value can also be |None|, indicating the picture either has no geometry (not
+ expected) or has custom geometry, like a freeform shape. A picture with no geometry will
+ have no visible representation on the slide, although it can be selected. This is because
+ without geometry, there is no "inside-the-shape" for it to appear in.
"""
prstGeom = self._pic.spPr.prstGeom
if prstGeom is None: # ---generally means cropped with freeform---
@@ -174,32 +169,29 @@ def auto_shape_type(self):
return prstGeom.prst
@auto_shape_type.setter
- def auto_shape_type(self, member):
+ def auto_shape_type(self, member: MSO_SHAPE):
MSO_SHAPE.validate(member)
spPr = self._pic.spPr
prstGeom = spPr.prstGeom
if prstGeom is None:
- spPr._remove_custGeom()
- prstGeom = spPr._add_prstGeom()
+ spPr._remove_custGeom() # pyright: ignore[reportPrivateUsage]
+ prstGeom = spPr._add_prstGeom() # pyright: ignore[reportPrivateUsage]
prstGeom.prst = member
@property
def image(self):
+ """The |Image| object for this picture.
+
+ Provides access to the properties and bytes of the image in this picture shape.
"""
- An |Image| object providing access to the properties and bytes of the
- image in this picture shape.
- """
- slide_part, rId = self.part, self._element.blip_rId
+ slide_part, rId = self.part, self._pic.blip_rId
if rId is None:
raise ValueError("no embedded image")
return slide_part.get_image(rId)
@property
- def shape_type(self):
- """
- Unique integer identifying the type of this shape, unconditionally
- ``MSO_SHAPE_TYPE.PICTURE`` in this case.
- """
+ def shape_type(self) -> MSO_SHAPE_TYPE:
+ """Unconditionally `MSO_SHAPE_TYPE.PICTURE` in this case."""
return MSO_SHAPE_TYPE.PICTURE
diff --git a/src/pptx/shapes/placeholder.py b/src/pptx/shapes/placeholder.py
index 791d91e13..c44837bef 100644
--- a/src/pptx/shapes/placeholder.py
+++ b/src/pptx/shapes/placeholder.py
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
"""Placeholder-related objects.
Specific to shapes having a `p:ph` element. A placeholder has distinct behaviors
@@ -7,6 +5,10 @@
non-trivial class inheritance structure.
"""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
from pptx.enum.shapes import MSO_SHAPE_TYPE, PP_PLACEHOLDER
from pptx.oxml.shapes.graphfrm import CT_GraphicalObjectFrame
from pptx.oxml.shapes.picture import CT_Picture
@@ -15,6 +17,9 @@
from pptx.shapes.picture import Picture
from pptx.util import Emu
+if TYPE_CHECKING:
+ from pptx.oxml.shapes.autoshape import CT_Shape
+
class _InheritsDimensions(object):
"""
@@ -208,13 +213,14 @@ def sz(self):
class LayoutPlaceholder(_InheritsDimensions, Shape):
- """
- Placeholder shape on a slide layout, providing differentiated behavior
- for slide layout placeholders, in particular, inheriting shape properties
- from the master placeholder having the same type, when a matching one
- exists.
+ """Placeholder shape on a slide layout.
+
+ Provides differentiated behavior for slide layout placeholders, in particular, inheriting
+ shape properties from the master placeholder having the same type, when a matching one exists.
"""
+ element: CT_Shape # pyright: ignore[reportIncompatibleMethodOverride]
+
@property
def _base_placeholder(self):
"""
@@ -241,9 +247,9 @@ def _base_placeholder(self):
class MasterPlaceholder(BasePlaceholder):
- """
- Placeholder shape on a slide master.
- """
+ """Placeholder shape on a slide master."""
+
+ element: CT_Shape # pyright: ignore[reportIncompatibleMethodOverride]
class NotesSlidePlaceholder(_InheritsDimensions, Shape):
@@ -299,9 +305,7 @@ def _new_chart_graphicFrame(self, rId, x, y, cx, cy):
position and size and containing the chart identified by *rId*.
"""
id_, name = self.shape_id, self.name
- return CT_GraphicalObjectFrame.new_chart_graphicFrame(
- id_, name, rId, x, y, cx, cy
- )
+ return CT_GraphicalObjectFrame.new_chart_graphicFrame(id_, name, rId, x, y, cx, cy)
class PicturePlaceholder(_BaseSlidePlaceholder):
diff --git a/src/pptx/shapes/shapetree.py b/src/pptx/shapes/shapetree.py
index cebdfc91f..29623f1f5 100644
--- a/src/pptx/shapes/shapetree.py
+++ b/src/pptx/shapes/shapetree.py
@@ -1,14 +1,16 @@
-# encoding: utf-8
-
"""The shape tree, the structure that holds a slide's shapes."""
+from __future__ import annotations
+
+import io
import os
+from typing import IO, TYPE_CHECKING, Callable, Iterable, Iterator, cast
-from pptx.compat import BytesIO
from pptx.enum.shapes import PP_PLACEHOLDER, PROG_ID
from pptx.media import SPEAKER_IMAGE_BYTES, Video
from pptx.opc.constants import CONTENT_TYPE as CT
from pptx.oxml.ns import qn
+from pptx.oxml.shapes.autoshape import CT_Shape
from pptx.oxml.shapes.graphfrm import CT_GraphicalObjectFrame
from pptx.oxml.shapes.picture import CT_Picture
from pptx.oxml.simpletypes import ST_Direction
@@ -33,6 +35,20 @@
from pptx.shared import ParentedElementProxy
from pptx.util import Emu, lazyproperty
+if TYPE_CHECKING:
+ from pptx.chart.chart import Chart
+ from pptx.chart.data import ChartData
+ from pptx.enum.chart import XL_CHART_TYPE
+ from pptx.enum.shapes import MSO_CONNECTOR_TYPE, MSO_SHAPE
+ from pptx.oxml.shapes import ShapeElement
+ from pptx.oxml.shapes.connector import CT_Connector
+ from pptx.oxml.shapes.groupshape import CT_GroupShape
+ from pptx.parts.image import ImagePart
+ from pptx.parts.slide import SlidePart
+ from pptx.slide import Slide, SlideLayout
+ from pptx.types import ProvidesPart
+ from pptx.util import Length
+
# +-- _BaseShapes
# | |
# | +-- _BaseGroupShapes
@@ -59,20 +75,18 @@
class _BaseShapes(ParentedElementProxy):
- """
- Base class for a shape collection appearing in a slide-type object,
- include Slide, SlideLayout, and SlideMaster, providing common methods.
+ """Base class for a shape collection appearing in a slide-type object.
+
+ Subclasses include Slide, SlideLayout, and SlideMaster. Provides common methods.
"""
- def __init__(self, spTree, parent):
+ def __init__(self, spTree: CT_GroupShape, parent: ProvidesPart):
super(_BaseShapes, self).__init__(spTree, parent)
self._spTree = spTree
self._cached_max_shape_id = None
- def __getitem__(self, idx):
- """
- Return shape at *idx* in sequence, e.g. ``shapes[2]``.
- """
+ def __getitem__(self, idx: int) -> BaseShape:
+ """Return shape at `idx` in sequence, e.g. `shapes[2]`."""
shape_elms = list(self._iter_member_elms())
try:
shape_elm = shape_elms[idx]
@@ -80,36 +94,33 @@ def __getitem__(self, idx):
raise IndexError("shape index out of range")
return self._shape_factory(shape_elm)
- def __iter__(self):
- """
- Generate a reference to each shape in the collection, in sequence.
- """
+ def __iter__(self) -> Iterator[BaseShape]:
+ """Generate a reference to each shape in the collection, in sequence."""
for shape_elm in self._iter_member_elms():
yield self._shape_factory(shape_elm)
- def __len__(self):
- """
- Return count of shapes in this shape tree. A group shape contributes
- 1 to the total, without regard to the number of shapes contained in
- the group.
+ def __len__(self) -> int:
+ """Return count of shapes in this shape tree.
+
+ A group shape contributes 1 to the total, without regard to the number of shapes contained
+ in the group.
"""
shape_elms = list(self._iter_member_elms())
return len(shape_elms)
- def clone_placeholder(self, placeholder):
- """Add a new placeholder shape based on *placeholder*."""
+ def clone_placeholder(self, placeholder: LayoutPlaceholder) -> None:
+ """Add a new placeholder shape based on `placeholder`."""
sp = placeholder.element
ph_type, orient, sz, idx = (sp.ph_type, sp.ph_orient, sp.ph_sz, sp.ph_idx)
id_ = self._next_shape_id
name = self._next_ph_name(ph_type, id_, orient)
self._spTree.add_placeholder(id_, name, ph_type, orient, sz, idx)
- def ph_basename(self, ph_type):
- """
- Return the base name for a placeholder of *ph_type* in this shape
- collection. There is some variance between slide types, for example
- a notes slide uses a different name for the body placeholder, so this
- method can be overriden by subclasses.
+ def ph_basename(self, ph_type: PP_PLACEHOLDER) -> str:
+ """Return the base name for a placeholder of `ph_type` in this shape collection.
+
+ There is some variance between slide types, for example a notes slide uses a different
+ name for the body placeholder, so this method can be overriden by subclasses.
"""
return {
PP_PLACEHOLDER.BITMAP: "ClipArt Placeholder",
@@ -130,60 +141,51 @@ def ph_basename(self, ph_type):
}[ph_type]
@property
- def turbo_add_enabled(self):
+ def turbo_add_enabled(self) -> bool:
"""True if "turbo-add" mode is enabled. Read/Write.
- EXPERIMENTAL: This feature can radically improve performance when
- adding large numbers (hundreds of shapes) to a slide. It works by
- caching the last shape ID used and incrementing that value to assign
- the next shape id. This avoids repeatedly searching all shape ids in
- the slide each time a new ID is required.
-
- Performance is not noticeably improved for a slide with a relatively
- small number of shapes, but because the search time rises with the
- square of the shape count, this option can be useful for optimizing
- generation of a slide composed of many shapes.
-
- Shape-id collisions can occur (causing a repair error on load) if
- more than one |Slide| object is used to interact with the same slide
- in the presentation. Note that the |Slides| collection creates a new
- |Slide| object each time a slide is accessed
- (e.g. `slide = prs.slides[0]`, so you must be careful to limit use to
- a single |Slide| object.
+ EXPERIMENTAL: This feature can radically improve performance when adding large numbers
+ (hundreds of shapes) to a slide. It works by caching the last shape ID used and
+ incrementing that value to assign the next shape id. This avoids repeatedly searching all
+ shape ids in the slide each time a new ID is required.
+
+ Performance is not noticeably improved for a slide with a relatively small number of
+ shapes, but because the search time rises with the square of the shape count, this option
+ can be useful for optimizing generation of a slide composed of many shapes.
+
+ Shape-id collisions can occur (causing a repair error on load) if more than one |Slide|
+ object is used to interact with the same slide in the presentation. Note that the |Slides|
+ collection creates a new |Slide| object each time a slide is accessed (e.g. `slide =
+ prs.slides[0]`, so you must be careful to limit use to a single |Slide| object.
"""
return self._cached_max_shape_id is not None
@turbo_add_enabled.setter
- def turbo_add_enabled(self, value):
+ def turbo_add_enabled(self, value: bool):
enable = bool(value)
self._cached_max_shape_id = self._spTree.max_shape_id if enable else None
@staticmethod
- def _is_member_elm(shape_elm):
- """
- Return true if *shape_elm* represents a member of this collection,
- False otherwise.
- """
+ def _is_member_elm(shape_elm: ShapeElement) -> bool:
+ """Return true if `shape_elm` represents a member of this collection, False otherwise."""
return True
- def _iter_member_elms(self):
- """
- Generate each child of the ```` element that corresponds to
- a shape, in the sequence they appear in the XML.
+ def _iter_member_elms(self) -> Iterator[ShapeElement]:
+ """Generate each child of the `p:spTree` element that corresponds to a shape.
+
+ Items appear in XML document order.
"""
for shape_elm in self._spTree.iter_shape_elms():
if self._is_member_elm(shape_elm):
yield shape_elm
- def _next_ph_name(self, ph_type, id, orient):
- """
- Next unique placeholder name for placeholder shape of type *ph_type*,
- with id number *id* and orientation *orient*. Usually will be standard
- placeholder root name suffixed with id-1, e.g.
- _next_ph_name(ST_PlaceholderType.TBL, 4, 'horz') ==>
- 'Table Placeholder 3'. The number is incremented as necessary to make
- the name unique within the collection. If *orient* is ``'vert'``, the
- placeholder name is prefixed with ``'Vertical '``.
+ def _next_ph_name(self, ph_type: PP_PLACEHOLDER, id: int, orient: str) -> str:
+ """Next unique placeholder name for placeholder shape of type `ph_type`.
+
+ Usually will be standard placeholder root name suffixed with id-1, e.g.
+ _next_ph_name(ST_PlaceholderType.TBL, 4, 'horz') ==> 'Table Placeholder 3'. The number is
+ incremented as necessary to make the name unique within the collection. If `orient` is
+ `'vert'`, the placeholder name is prefixed with `'Vertical '`.
"""
basename = self.ph_basename(ph_type)
@@ -203,12 +205,11 @@ def _next_ph_name(self, ph_type, id, orient):
return name
@property
- def _next_shape_id(self):
+ def _next_shape_id(self) -> int:
"""Return a unique shape id suitable for use with a new shape.
- The returned id is 1 greater than the maximum shape id used so far.
- In practice, the minimum id is 2 because the spTree element is always
- assigned id="1".
+ The returned id is 1 greater than the maximum shape id used so far. In practice, the
+ minimum id is 2 because the spTree element is always assigned id="1".
"""
# ---presence of cached-max-shape-id indicates turbo mode is on---
if self._cached_max_shape_id is not None:
@@ -217,108 +218,120 @@ def _next_shape_id(self):
return self._spTree.max_shape_id + 1
- def _shape_factory(self, shape_elm):
- """
- Return an instance of the appropriate shape proxy class for
- *shape_elm*.
- """
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
return BaseShapeFactory(shape_elm, self)
class _BaseGroupShapes(_BaseShapes):
"""Base class for shape-trees that can add shapes."""
- def __init__(self, grpSp, parent):
+ part: SlidePart # pyright: ignore[reportIncompatibleMethodOverride]
+ _element: CT_GroupShape
+
+ def __init__(self, grpSp: CT_GroupShape, parent: ProvidesPart):
super(_BaseGroupShapes, self).__init__(grpSp, parent)
self._grpSp = grpSp
- def add_chart(self, chart_type, x, y, cx, cy, chart_data):
- """Add a new chart of *chart_type* to the slide.
-
- The chart is positioned at (*x*, *y*), has size (*cx*, *cy*), and
- depicts *chart_data*. *chart_type* is one of the :ref:`XlChartType`
- enumeration values. *chart_data* is a |ChartData| object populated
- with the categories and series values for the chart.
-
- Note that a |GraphicFrame| shape object is returned, not the |Chart|
- object contained in that graphic frame shape. The chart object may be
- accessed using the :attr:`chart` property of the returned
- |GraphicFrame| object.
+ def add_chart(
+ self,
+ chart_type: XL_CHART_TYPE,
+ x: Length,
+ y: Length,
+ cx: Length,
+ cy: Length,
+ chart_data: ChartData,
+ ) -> Chart:
+ """Add a new chart of `chart_type` to the slide.
+
+ The chart is positioned at (`x`, `y`), has size (`cx`, `cy`), and depicts `chart_data`.
+ `chart_type` is one of the :ref:`XlChartType` enumeration values. `chart_data` is a
+ |ChartData| object populated with the categories and series values for the chart.
+
+ Note that a |GraphicFrame| shape object is returned, not the |Chart| object contained in
+ that graphic frame shape. The chart object may be accessed using the :attr:`chart`
+ property of the returned |GraphicFrame| object.
"""
rId = self.part.add_chart_part(chart_type, chart_data)
graphicFrame = self._add_chart_graphicFrame(rId, x, y, cx, cy)
self._recalculate_extents()
- return self._shape_factory(graphicFrame)
+ return cast("Chart", self._shape_factory(graphicFrame))
- def add_connector(self, connector_type, begin_x, begin_y, end_x, end_y):
+ def add_connector(
+ self,
+ connector_type: MSO_CONNECTOR_TYPE,
+ begin_x: Length,
+ begin_y: Length,
+ end_x: Length,
+ end_y: Length,
+ ) -> Connector:
"""Add a newly created connector shape to the end of this shape tree.
- *connector_type* is a member of the :ref:`MsoConnectorType`
- enumeration and the end-point values are specified as EMU values. The
- returned connector is of type *connector_type* and has begin and end
- points as specified.
+ `connector_type` is a member of the :ref:`MsoConnectorType` enumeration and the end-point
+ values are specified as EMU values. The returned connector is of type `connector_type` and
+ has begin and end points as specified.
"""
cxnSp = self._add_cxnSp(connector_type, begin_x, begin_y, end_x, end_y)
self._recalculate_extents()
- return self._shape_factory(cxnSp)
+ return cast(Connector, self._shape_factory(cxnSp))
- def add_group_shape(self, shapes=[]):
+ def add_group_shape(self, shapes: Iterable[BaseShape] = ()) -> GroupShape:
"""Return a |GroupShape| object newly appended to this shape tree.
- The group shape is empty and must be populated with shapes using
- methods on its shape tree, available on its `.shapes` property. The
- position and extents of the group shape are determined by the shapes
- it contains; its position and extents are recalculated each time
+ The group shape is empty and must be populated with shapes using methods on its shape
+ tree, available on its `.shapes` property. The position and extents of the group shape are
+ determined by the shapes it contains; its position and extents are recalculated each time
a shape is added to it.
"""
+ shapes = tuple(shapes)
grpSp = self._element.add_grpSp()
for shape in shapes:
- grpSp.insert_element_before(shape._element, "p:extLst")
+ grpSp.insert_element_before(
+ shape._element, "p:extLst" # pyright: ignore[reportPrivateUsage]
+ )
if shapes:
grpSp.recalculate_extents()
- return self._shape_factory(grpSp)
+ return cast(GroupShape, self._shape_factory(grpSp))
def add_ole_object(
self,
- object_file,
- prog_id,
- left,
- top,
- width=None,
- height=None,
- icon_file=None,
- icon_width=None,
- icon_height=None,
- ):
+ object_file: str | IO[bytes],
+ prog_id: str,
+ left: Length,
+ top: Length,
+ width: Length | None = None,
+ height: Length | None = None,
+ icon_file: str | IO[bytes] | None = None,
+ icon_width: Length | None = None,
+ icon_height: Length | None = None,
+ ) -> GraphicFrame:
"""Return newly-created GraphicFrame shape embedding `object_file`.
- The returned graphic-frame shape contains `object_file` as an embedded OLE
- object. It is displayed as an icon at `left`, `top` with size `width`, `height`.
- `width` and `height` may be omitted when `prog_id` is a member of `PROG_ID`, in
- which case the default icon size is used. This is advised for best appearance
- where applicable because it avoids an icon with a "stretched" appearance.
+ The returned graphic-frame shape contains `object_file` as an embedded OLE object. It is
+ displayed as an icon at `left`, `top` with size `width`, `height`. `width` and `height`
+ may be omitted when `prog_id` is a member of `PROG_ID`, in which case the default icon
+ size is used. This is advised for best appearance where applicable because it avoids an
+ icon with a "stretched" appearance.
`object_file` may either be a str path to a file or file-like object (such as
- `io.BytesIO`) containing the bytes of the object to be embedded (such as an
- Excel file).
-
- `prog_id` can be either a member of `pptx.enum.shapes.PROG_ID` or a str value
- like `"Adobe.Exchange.7"` determined by inspecting the XML generated by
- PowerPoint for an object of the desired type.
-
- `icon_file` may either be a str path to an image file or a file-like object
- containing the image. The image provided will be displayed in lieu of the OLE
- object; double-clicking on the image opens the object (subject to
- operating-system limitations). The image file can be any supported image file.
- Those produced by PowerPoint itself are generally EMF and can be harvested from
- a PPTX package that embeds such an object. PNG and JPG also work fine.
-
- `icon_width` and `icon_height` are `Length` values (e.g. Emu() or Inches()) that
- describe the size of the icon image within the shape. These should be omitted
- unless a custom `icon_file` is provided. The dimensions must be discovered by
- inspecting the XML. Automatic resizing of the OLE-object shape can occur when
- the icon is double-clicked if these values are not as set by PowerPoint. This
- behavior may only manifest in the Windows version of PowerPoint.
+ `io.BytesIO`) containing the bytes of the object to be embedded (such as an Excel file).
+
+ `prog_id` can be either a member of `pptx.enum.shapes.PROG_ID` or a str value like
+ `"Adobe.Exchange.7"` determined by inspecting the XML generated by PowerPoint for an
+ object of the desired type.
+
+ `icon_file` may either be a str path to an image file or a file-like object containing the
+ image. The image provided will be displayed in lieu of the OLE object; double-clicking on
+ the image opens the object (subject to operating-system limitations). The image file can
+ be any supported image file. Those produced by PowerPoint itself are generally EMF and can
+ be harvested from a PPTX package that embeds such an object. PNG and JPG also work fine.
+
+ `icon_width` and `icon_height` are `Length` values (e.g. Emu() or Inches()) that describe
+ the size of the icon image within the shape. These should be omitted unless a custom
+ `icon_file` is provided. The dimensions must be discovered by inspecting the XML.
+ Automatic resizing of the OLE-object shape can occur when the icon is double-clicked if
+ these values are not as set by PowerPoint. This behavior may only manifest in the Windows
+ version of PowerPoint.
"""
graphicFrame = _OleObjectElementCreator.graphicFrame(
self,
@@ -335,85 +348,91 @@ def add_ole_object(
)
self._spTree.append(graphicFrame)
self._recalculate_extents()
- return self._shape_factory(graphicFrame)
-
- def add_picture(self, image_file, left, top, width=None, height=None):
- """Add picture shape displaying image in *image_file*.
-
- *image_file* can be either a path to a file (a string) or a file-like
- object. The picture is positioned with its top-left corner at (*top*,
- *left*). If *width* and *height* are both |None|, the native size of
- the image is used. If only one of *width* or *height* is used, the
- unspecified dimension is calculated to preserve the aspect ratio of
- the image. If both are specified, the picture is stretched to fit,
- without regard to its native aspect ratio.
+ return cast(GraphicFrame, self._shape_factory(graphicFrame))
+
+ def add_picture(
+ self,
+ image_file: str | IO[bytes],
+ left: Length,
+ top: Length,
+ width: Length | None = None,
+ height: Length | None = None,
+ ) -> Picture:
+ """Add picture shape displaying image in `image_file`.
+
+ `image_file` can be either a path to a file (a string) or a file-like object. The picture
+ is positioned with its top-left corner at (`top`, `left`). If `width` and `height` are
+ both |None|, the native size of the image is used. If only one of `width` or `height` is
+ used, the unspecified dimension is calculated to preserve the aspect ratio of the image.
+ If both are specified, the picture is stretched to fit, without regard to its native
+ aspect ratio.
"""
image_part, rId = self.part.get_or_add_image_part(image_file)
pic = self._add_pic_from_image_part(image_part, rId, left, top, width, height)
self._recalculate_extents()
- return self._shape_factory(pic)
+ return cast(Picture, self._shape_factory(pic))
- def add_shape(self, autoshape_type_id, left, top, width, height):
+ def add_shape(
+ self, autoshape_type_id: MSO_SHAPE, left: Length, top: Length, width: Length, height: Length
+ ) -> Shape:
"""Return new |Shape| object appended to this shape tree.
- *autoshape_type_id* is a member of :ref:`MsoAutoShapeType` e.g.
- ``MSO_SHAPE.RECTANGLE`` specifying the type of shape to be added. The
- remaining arguments specify the new shape's position and size.
+ `autoshape_type_id` is a member of :ref:`MsoAutoShapeType` e.g. `MSO_SHAPE.RECTANGLE`
+ specifying the type of shape to be added. The remaining arguments specify the new shape's
+ position and size.
"""
autoshape_type = AutoShapeType(autoshape_type_id)
sp = self._add_sp(autoshape_type, left, top, width, height)
self._recalculate_extents()
- return self._shape_factory(sp)
+ return cast(Shape, self._shape_factory(sp))
- def add_textbox(self, left, top, width, height):
+ def add_textbox(self, left: Length, top: Length, width: Length, height: Length) -> Shape:
"""Return newly added text box shape appended to this shape tree.
- The text box is of the specified size, located at the specified
- position on the slide.
+ The text box is of the specified size, located at the specified position on the slide.
"""
sp = self._add_textbox_sp(left, top, width, height)
self._recalculate_extents()
- return self._shape_factory(sp)
+ return cast(Shape, self._shape_factory(sp))
- def build_freeform(self, start_x=0, start_y=0, scale=1.0):
+ def build_freeform(
+ self, start_x: float = 0, start_y: float = 0, scale: tuple[float, float] | float = 1.0
+ ) -> FreeformBuilder:
"""Return |FreeformBuilder| object to specify a freeform shape.
- The optional *start_x* and *start_y* arguments specify the starting
- pen position in local coordinates. They will be rounded to the
- nearest integer before use and each default to zero.
-
- The optional *scale* argument specifies the size of local coordinates
- proportional to slide coordinates (EMU). If the vertical scale is
- different than the horizontal scale (local coordinate units are
- "rectangular"), a pair of numeric values can be provided as the
- *scale* argument, e.g. `scale=(1.0, 2.0)`. In this case the first
- number is interpreted as the horizontal (X) scale and the second as
- the vertical (Y) scale.
-
- A convenient method for calculating scale is to divide a |Length|
- object by an equivalent count of local coordinate units, e.g.
- `scale = Inches(1)/1000` for 1000 local units per inch.
+ The optional `start_x` and `start_y` arguments specify the starting pen position in local
+ coordinates. They will be rounded to the nearest integer before use and each default to
+ zero.
+
+ The optional `scale` argument specifies the size of local coordinates proportional to
+ slide coordinates (EMU). If the vertical scale is different than the horizontal scale
+ (local coordinate units are "rectangular"), a pair of numeric values can be provided as
+ the `scale` argument, e.g. `scale=(1.0, 2.0)`. In this case the first number is
+ interpreted as the horizontal (X) scale and the second as the vertical (Y) scale.
+
+ A convenient method for calculating scale is to divide a |Length| object by an equivalent
+ count of local coordinate units, e.g. `scale = Inches(1)/1000` for 1000 local units per
+ inch.
"""
- try:
- x_scale, y_scale = scale
- except TypeError:
- x_scale = y_scale = scale
+ x_scale, y_scale = scale if isinstance(scale, tuple) else (scale, scale)
return FreeformBuilder.new(self, start_x, start_y, x_scale, y_scale)
- def index(self, shape):
- """Return the index of *shape* in this sequence.
+ def index(self, shape: BaseShape) -> int:
+ """Return the index of `shape` in this sequence.
- Raises |ValueError| if *shape* is not in the collection.
+ Raises |ValueError| if `shape` is not in the collection.
"""
shape_elms = list(self._element.iter_shape_elms())
return shape_elms.index(shape.element)
- def _add_chart_graphicFrame(self, rId, x, y, cx, cy):
+ def _add_chart_graphicFrame(
+ self, rId: str, x: Length, y: Length, cx: Length, cy: Length
+ ) -> CT_GraphicalObjectFrame:
"""Return new `p:graphicFrame` element appended to this shape tree.
- The `p:graphicFrame` element has the specified position and size and
- refers to the chart part identified by *rId*.
+ The `p:graphicFrame` element has the specified position and size and refers to the chart
+ part identified by `rId`.
"""
shape_id = self._next_shape_id
name = "Chart %d" % (shape_id - 1)
@@ -423,12 +442,18 @@ def _add_chart_graphicFrame(self, rId, x, y, cx, cy):
self._spTree.append(graphicFrame)
return graphicFrame
- def _add_cxnSp(self, connector_type, begin_x, begin_y, end_x, end_y):
+ def _add_cxnSp(
+ self,
+ connector_type: MSO_CONNECTOR_TYPE,
+ begin_x: Length,
+ begin_y: Length,
+ end_x: Length,
+ end_y: Length,
+ ) -> CT_Connector:
"""Return a newly-added `p:cxnSp` element as specified.
- The `p:cxnSp` element is for a connector of *connector_type*
- beginning at (*begin_x*, *begin_y*) and extending to
- (*end_x*, *end_y*).
+ The `p:cxnSp` element is for a connector of `connector_type` beginning at (`begin_x`,
+ `begin_y`) and extending to (`end_x`, `end_y`).
"""
id_ = self._next_shape_id
name = "Connector %d" % (id_ - 1)
@@ -439,13 +464,20 @@ def _add_cxnSp(self, connector_type, begin_x, begin_y, end_x, end_y):
return self._element.add_cxnSp(id_, name, connector_type, x, y, cx, cy, flipH, flipV)
- def _add_pic_from_image_part(self, image_part, rId, x, y, cx, cy):
+ def _add_pic_from_image_part(
+ self,
+ image_part: ImagePart,
+ rId: str,
+ x: Length,
+ y: Length,
+ cx: Length | None,
+ cy: Length | None,
+ ) -> CT_Picture:
"""Return a newly appended `p:pic` element as specified.
- The `p:pic` element displays the image in *image_part* with size and
- position specified by *x*, *y*, *cx*, and *cy*. The element is
- appended to the shape tree, causing it to be displayed first in
- z-order on the slide.
+ The `p:pic` element displays the image in `image_part` with size and position specified by
+ `x`, `y`, `cx`, and `cy`. The element is appended to the shape tree, causing it to be
+ displayed first in z-order on the slide.
"""
id_ = self._next_shape_id
scaled_cx, scaled_cy = image_part.scale(cx, cy)
@@ -454,32 +486,33 @@ def _add_pic_from_image_part(self, image_part, rId, x, y, cx, cy):
pic = self._grpSp.add_pic(id_, name, desc, rId, x, y, scaled_cx, scaled_cy)
return pic
- def _add_sp(self, autoshape_type, x, y, cx, cy):
+ def _add_sp(
+ self, autoshape_type: AutoShapeType, x: Length, y: Length, cx: Length, cy: Length
+ ) -> CT_Shape:
"""Return newly-added `p:sp` element as specified.
- `p:sp` element is of *autoshape_type* at position (*x*, *y*) and of
- size (*cx*, *cy*).
+ `p:sp` element is of `autoshape_type` at position (`x`, `y`) and of size (`cx`, `cy`).
"""
id_ = self._next_shape_id
name = "%s %d" % (autoshape_type.basename, id_ - 1)
sp = self._grpSp.add_autoshape(id_, name, autoshape_type.prst, x, y, cx, cy)
return sp
- def _add_textbox_sp(self, x, y, cx, cy):
+ def _add_textbox_sp(self, x: Length, y: Length, cx: Length, cy: Length) -> CT_Shape:
"""Return newly-appended textbox `p:sp` element.
- Element has position (*x*, *y*) and size (*cx*, *cy*).
+ Element has position (`x`, `y`) and size (`cx`, `cy`).
"""
id_ = self._next_shape_id
name = "TextBox %d" % (id_ - 1)
sp = self._spTree.add_textbox(id_, name, x, y, cx, cy)
return sp
- def _recalculate_extents(self):
+ def _recalculate_extents(self) -> None:
"""Adjust position and size to incorporate all contained shapes.
- This would typically be called when a contained shape is added,
- removed, or its position or size updated.
+ This would typically be called when a contained shape is added, removed, or its position
+ or size updated.
"""
# ---default behavior is to do nothing, GroupShapes overrides to
# produce the distinctive behavior of groups and subgroups.---
@@ -489,15 +522,15 @@ def _recalculate_extents(self):
class GroupShapes(_BaseGroupShapes):
"""The sequence of child shapes belonging to a group shape.
- Note that this collection can itself contain a group shape, making this
- part of a recursive, tree data structure (acyclic graph).
+ Note that this collection can itself contain a group shape, making this part of a recursive,
+ tree data structure (acyclic graph).
"""
- def _recalculate_extents(self):
+ def _recalculate_extents(self) -> None:
"""Adjust position and size to incorporate all contained shapes.
- This would typically be called when a contained shape is added,
- removed, or its position or size updated.
+ This would typically be called when a contained shape is added, removed, or its position
+ or size updated.
"""
self._grpSp.recalculate_extents()
@@ -505,38 +538,38 @@ def _recalculate_extents(self):
class SlideShapes(_BaseGroupShapes):
"""Sequence of shapes appearing on a slide.
- The first shape in the sequence is the backmost in z-order and the last
- shape is topmost. Supports indexed access, len(), index(), and iteration.
+ The first shape in the sequence is the backmost in z-order and the last shape is topmost.
+ Supports indexed access, len(), index(), and iteration.
"""
+ parent: Slide # pyright: ignore[reportIncompatibleMethodOverride]
+
def add_movie(
self,
- movie_file,
- left,
- top,
- width,
- height,
- poster_frame_image=None,
- mime_type=CT.VIDEO,
- ):
- """Return newly added movie shape displaying video in *movie_file*.
+ movie_file: str | IO[bytes],
+ left: Length,
+ top: Length,
+ width: Length,
+ height: Length,
+ poster_frame_image: str | IO[bytes] | None = None,
+ mime_type: str = CT.VIDEO,
+ ) -> GraphicFrame:
+ """Return newly added movie shape displaying video in `movie_file`.
**EXPERIMENTAL.** This method has important limitations:
- * The size must be specified; no auto-scaling such as that provided
- by :meth:`add_picture` is performed.
- * The MIME type of the video file should be specified, e.g.
- 'video/mp4'. The provided video file is not interrogated for its
- type. The MIME type `video/unknown` is used by default (and works
- fine in tests as of this writing).
- * A poster frame image must be provided, it cannot be automatically
- extracted from the video file. If no poster frame is provided, the
- default "media loudspeaker" image will be used.
-
- Return a newly added movie shape to the slide, positioned at (*left*,
- *top*), having size (*width*, *height*), and containing *movie_file*.
- Before the video is started, *poster_frame_image* is displayed as
- a placeholder for the video.
+ * The size must be specified; no auto-scaling such as that provided by :meth:`add_picture`
+ is performed.
+ * The MIME type of the video file should be specified, e.g. 'video/mp4'. The provided
+ video file is not interrogated for its type. The MIME type `video/unknown` is used by
+ default (and works fine in tests as of this writing).
+ * A poster frame image must be provided, it cannot be automatically extracted from the
+ video file. If no poster frame is provided, the default "media loudspeaker" image will
+ be used.
+
+ Return a newly added movie shape to the slide, positioned at (`left`, `top`), having size
+ (`width`, `height`), and containing `movie_file`. Before the video is started,
+ `poster_frame_image` is displayed as a placeholder for the video.
"""
movie_pic = _MoviePicElementCreator.new_movie_pic(
self,
@@ -551,120 +584,106 @@ def add_movie(
)
self._spTree.append(movie_pic)
self._add_video_timing(movie_pic)
- return self._shape_factory(movie_pic)
+ return cast(GraphicFrame, self._shape_factory(movie_pic))
- def add_table(self, rows, cols, left, top, width, height):
- """
- Add a |GraphicFrame| object containing a table with the specified
- number of *rows* and *cols* and the specified position and size.
- *width* is evenly distributed between the columns of the new table.
- Likewise, *height* is evenly distributed between the rows. Note that
- the ``.table`` property on the returned |GraphicFrame| shape must be
- used to access the enclosed |Table| object.
+ def add_table(
+ self, rows: int, cols: int, left: Length, top: Length, width: Length, height: Length
+ ) -> GraphicFrame:
+ """Add a |GraphicFrame| object containing a table.
+
+ The table has the specified number of `rows` and `cols` and the specified position and
+ size. `width` is evenly distributed between the columns of the new table. Likewise,
+ `height` is evenly distributed between the rows. Note that the `.table` property on the
+ returned |GraphicFrame| shape must be used to access the enclosed |Table| object.
"""
graphicFrame = self._add_graphicFrame_containing_table(rows, cols, left, top, width, height)
- graphic_frame = self._shape_factory(graphicFrame)
- return graphic_frame
+ return cast(GraphicFrame, self._shape_factory(graphicFrame))
- def clone_layout_placeholders(self, slide_layout):
- """
- Add placeholder shapes based on those in *slide_layout*. Z-order of
- placeholders is preserved. Latent placeholders (date, slide number,
- and footer) are not cloned.
+ def clone_layout_placeholders(self, slide_layout: SlideLayout) -> None:
+ """Add placeholder shapes based on those in `slide_layout`.
+
+ Z-order of placeholders is preserved. Latent placeholders (date, slide number, and footer)
+ are not cloned.
"""
for placeholder in slide_layout.iter_cloneable_placeholders():
self.clone_placeholder(placeholder)
@property
- def placeholders(self):
- """
- Instance of |SlidePlaceholders| containing sequence of placeholder
- shapes in this slide.
- """
+ def placeholders(self) -> SlidePlaceholders:
+ """Sequence of placeholder shapes in this slide."""
return self.parent.placeholders
@property
- def title(self):
- """
- The title placeholder shape on the slide or |None| if the slide has
- no title placeholder.
+ def title(self) -> Shape | None:
+ """The title placeholder shape on the slide.
+
+ |None| if the slide has no title placeholder.
"""
for elm in self._spTree.iter_ph_elms():
if elm.ph_idx == 0:
- return self._shape_factory(elm)
+ return cast(Shape, self._shape_factory(elm))
return None
- def _add_graphicFrame_containing_table(self, rows, cols, x, y, cx, cy):
- """
- Return a newly added ```` element containing a table
- as specified by the parameters.
- """
+ def _add_graphicFrame_containing_table(
+ self, rows: int, cols: int, x: Length, y: Length, cx: Length, cy: Length
+ ) -> CT_GraphicalObjectFrame:
+ """Return a newly added `p:graphicFrame` element containing a table as specified."""
_id = self._next_shape_id
name = "Table %d" % (_id - 1)
graphicFrame = self._spTree.add_table(_id, name, rows, cols, x, y, cx, cy)
return graphicFrame
- def _add_video_timing(self, pic):
+ def _add_video_timing(self, pic: CT_Picture) -> None:
"""Add a `p:video` element under `p:sld/p:timing`.
- The element will refer to the specified *pic* element by its shape
- id, and cause the video play controls to appear for that video.
+ The element will refer to the specified `pic` element by its shape id, and cause the video
+ play controls to appear for that video.
"""
sld = self._spTree.xpath("/p:sld")[0]
childTnLst = sld.get_or_add_childTnLst()
childTnLst.add_video(pic.shape_id)
- def _shape_factory(self, shape_elm):
- """
- Return an instance of the appropriate shape proxy class for
- *shape_elm*.
- """
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
return SlideShapeFactory(shape_elm, self)
class LayoutShapes(_BaseShapes):
- """
- Sequence of shapes appearing on a slide layout. The first shape in the
- sequence is the backmost in z-order and the last shape is topmost.
+ """Sequence of shapes appearing on a slide layout.
+
+ The first shape in the sequence is the backmost in z-order and the last shape is topmost.
Supports indexed access, len(), index(), and iteration.
"""
- def _shape_factory(self, shape_elm):
- """
- Return an instance of the appropriate shape proxy class for
- *shape_elm*.
- """
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
return _LayoutShapeFactory(shape_elm, self)
class MasterShapes(_BaseShapes):
- """
- Sequence of shapes appearing on a slide master. The first shape in the
- sequence is the backmost in z-order and the last shape is topmost.
+ """Sequence of shapes appearing on a slide master.
+
+ The first shape in the sequence is the backmost in z-order and the last shape is topmost.
Supports indexed access, len(), and iteration.
"""
- def _shape_factory(self, shape_elm):
- """
- Return an instance of the appropriate shape proxy class for
- *shape_elm*.
- """
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
return _MasterShapeFactory(shape_elm, self)
class NotesSlideShapes(_BaseShapes):
- """
- Sequence of shapes appearing on a notes slide. The first shape in the
- sequence is the backmost in z-order and the last shape is topmost.
+ """Sequence of shapes appearing on a notes slide.
+
+ The first shape in the sequence is the backmost in z-order and the last shape is topmost.
Supports indexed access, len(), index(), and iteration.
"""
- def ph_basename(self, ph_type):
- """
- Return the base name for a placeholder of *ph_type* in this shape
- collection. A notes slide uses a different name for the body
- placeholder and has some unique placeholder types, so this
- method overrides the default in the base class.
+ def ph_basename(self, ph_type: PP_PLACEHOLDER) -> str:
+ """Return the base name for a placeholder of `ph_type` in this shape collection.
+
+ A notes slide uses a different name for the body placeholder and has some unique
+ placeholder types, so this method overrides the default in the base class.
"""
return {
PP_PLACEHOLDER.BODY: "Notes Placeholder",
@@ -675,105 +694,96 @@ def ph_basename(self, ph_type):
PP_PLACEHOLDER.SLIDE_NUMBER: "Slide Number Placeholder",
}[ph_type]
- def _shape_factory(self, shape_elm):
- """
- Return an instance of the appropriate shape proxy class for
- *shape_elm* appearing on a notes slide.
- """
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
+ """Return appropriate shape object for `shape_elm` appearing on a notes slide."""
return _NotesSlideShapeFactory(shape_elm, self)
class BasePlaceholders(_BaseShapes):
- """
- Base class for placeholder collections that differentiate behaviors for
- a master, layout, and slide. By default, placeholder shapes are
- constructed using |BaseShapeFactory|. Subclasses should override
+ """Base class for placeholder collections.
+
+ Subclasses differentiate behaviors for a master, layout, and slide. By default, placeholder
+ shapes are constructed using |BaseShapeFactory|. Subclasses should override
:method:`_shape_factory` to use custom placeholder classes.
"""
@staticmethod
- def _is_member_elm(shape_elm):
- """
- True if *shape_elm* is a placeholder shape, False otherwise.
- """
+ def _is_member_elm(shape_elm: ShapeElement) -> bool:
+ """True if `shape_elm` is a placeholder shape, False otherwise."""
return shape_elm.has_ph_elm
class LayoutPlaceholders(BasePlaceholders):
- """
- Sequence of |LayoutPlaceholder| instances representing the placeholder
- shapes on a slide layout.
- """
+ """Sequence of |LayoutPlaceholder| instance for each placeholder shape on a slide layout."""
- def get(self, idx, default=None):
- """
- Return the first placeholder shape with matching *idx* value, or
- *default* if not found.
- """
+ __iter__: Callable[ # pyright: ignore[reportIncompatibleMethodOverride]
+ [], Iterator[LayoutPlaceholder]
+ ]
+
+ def get(self, idx: int, default: LayoutPlaceholder | None = None) -> LayoutPlaceholder | None:
+ """The first placeholder shape with matching `idx` value, or `default` if not found."""
for placeholder in self:
if placeholder.element.ph_idx == idx:
return placeholder
return default
- def _shape_factory(self, shape_elm):
- """
- Return an instance of the appropriate shape proxy class for
- *shape_elm*.
- """
+ def _shape_factory(self, shape_elm: ShapeElement) -> BaseShape:
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
return _LayoutShapeFactory(shape_elm, self)
class MasterPlaceholders(BasePlaceholders):
- """
- Sequence of _MasterPlaceholder instances representing the placeholder
- shapes on a slide master.
- """
+ """Sequence of MasterPlaceholder representing the placeholder shapes on a slide master."""
- def get(self, ph_type, default=None):
- """
- Return the first placeholder shape with type *ph_type* (e.g. 'body'),
- or *default* if no such placeholder shape is present in the
- collection.
+ __iter__: Callable[ # pyright: ignore[reportIncompatibleMethodOverride]
+ [], Iterator[MasterPlaceholder]
+ ]
+
+ def get(self, ph_type: PP_PLACEHOLDER, default: MasterPlaceholder | None = None):
+ """Return the first placeholder shape with type `ph_type` (e.g. 'body').
+
+ Returns `default` if no such placeholder shape is present in the collection.
"""
for placeholder in self:
if placeholder.ph_type == ph_type:
return placeholder
return default
- def _shape_factory(self, shape_elm):
- """
- Return an instance of the appropriate shape proxy class for
- *shape_elm*.
- """
- return _MasterShapeFactory(shape_elm, self)
+ def _shape_factory( # pyright: ignore[reportIncompatibleMethodOverride]
+ self, placeholder_elm: CT_Shape
+ ) -> MasterPlaceholder:
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
+ return cast(MasterPlaceholder, _MasterShapeFactory(placeholder_elm, self))
class NotesSlidePlaceholders(MasterPlaceholders):
- """
- Sequence of placeholder shapes on a notes slide.
- """
+ """Sequence of placeholder shapes on a notes slide."""
- def _shape_factory(self, placeholder_elm):
- """
- Return an instance of the appropriate placeholder proxy class for
- *placeholder_elm*.
- """
- return _NotesSlideShapeFactory(placeholder_elm, self)
+ __iter__: Callable[ # pyright: ignore[reportIncompatibleMethodOverride]
+ [], Iterator[NotesSlidePlaceholder]
+ ]
+
+ def _shape_factory( # pyright: ignore[reportIncompatibleMethodOverride]
+ self, placeholder_elm: CT_Shape
+ ) -> NotesSlidePlaceholder:
+ """Return an instance of the appropriate placeholder proxy class for `placeholder_elm`."""
+ return cast(NotesSlidePlaceholder, _NotesSlideShapeFactory(placeholder_elm, self))
class SlidePlaceholders(ParentedElementProxy):
- """
- Collection of placeholder shapes on a slide. Supports iteration,
- :func:`len`, and dictionary-style lookup on the `idx` value of the
+ """Collection of placeholder shapes on a slide.
+
+ Supports iteration, :func:`len`, and dictionary-style lookup on the `idx` value of the
placeholders it contains.
"""
- def __getitem__(self, idx):
- """
- Access placeholder shape having *idx*. Note that while this looks
- like list access, idx is actually a dictionary key and will raise
- |KeyError| if no placeholder with that idx value is in the
- collection.
+ _element: CT_GroupShape
+
+ def __getitem__(self, idx: int):
+ """Access placeholder shape having `idx`.
+
+ Note that while this looks like list access, idx is actually a dictionary key and will
+ raise |KeyError| if no placeholder with that idx value is in the collection.
"""
for e in self._element.iter_ph_elms():
if e.ph_idx == idx:
@@ -781,26 +791,20 @@ def __getitem__(self, idx):
raise KeyError("no placeholder on this slide with idx == %d" % idx)
def __iter__(self):
- """
- Generate placeholder shapes in `idx` order.
- """
+ """Generate placeholder shapes in `idx` order."""
ph_elms = sorted([e for e in self._element.iter_ph_elms()], key=lambda e: e.ph_idx)
return (SlideShapeFactory(e, self) for e in ph_elms)
- def __len__(self):
- """
- Return count of placeholder shapes.
- """
+ def __len__(self) -> int:
+ """Return count of placeholder shapes."""
return len(list(self._element.iter_ph_elms()))
-def BaseShapeFactory(shape_elm, parent):
- """
- Return an instance of the appropriate shape proxy class for *shape_elm*.
- """
+def BaseShapeFactory(shape_elm: ShapeElement, parent: ProvidesPart) -> BaseShape:
+ """Return an instance of the appropriate shape proxy class for `shape_elm`."""
tag = shape_elm.tag
- if tag == qn("p:pic"):
+ if isinstance(shape_elm, CT_Picture):
videoFiles = shape_elm.xpath("./p:nvPicPr/p:nvPr/a:videoFile")
if videoFiles:
return Movie(shape_elm, parent)
@@ -813,46 +817,32 @@ def BaseShapeFactory(shape_elm, parent):
qn("p:graphicFrame"): GraphicFrame,
}.get(tag, BaseShape)
- return shape_cls(shape_elm, parent)
+ return shape_cls(shape_elm, parent) # pyright: ignore[reportArgumentType]
-def _LayoutShapeFactory(shape_elm, parent):
- """
- Return an instance of the appropriate shape proxy class for *shape_elm*
- on a slide layout.
- """
- tag_name = shape_elm.tag
- if tag_name == qn("p:sp") and shape_elm.has_ph_elm:
+def _LayoutShapeFactory(shape_elm: ShapeElement, parent: ProvidesPart) -> BaseShape:
+ """Return appropriate shape object for `shape_elm` on a slide layout."""
+ if isinstance(shape_elm, CT_Shape) and shape_elm.has_ph_elm:
return LayoutPlaceholder(shape_elm, parent)
return BaseShapeFactory(shape_elm, parent)
-def _MasterShapeFactory(shape_elm, parent):
- """
- Return an instance of the appropriate shape proxy class for *shape_elm*
- on a slide master.
- """
- tag_name = shape_elm.tag
- if tag_name == qn("p:sp") and shape_elm.has_ph_elm:
+def _MasterShapeFactory(shape_elm: ShapeElement, parent: ProvidesPart) -> BaseShape:
+ """Return appropriate shape object for `shape_elm` on a slide master."""
+ if isinstance(shape_elm, CT_Shape) and shape_elm.has_ph_elm:
return MasterPlaceholder(shape_elm, parent)
return BaseShapeFactory(shape_elm, parent)
-def _NotesSlideShapeFactory(shape_elm, parent):
- """
- Return an instance of the appropriate shape proxy class for *shape_elm*
- on a notes slide.
- """
- tag_name = shape_elm.tag
- if tag_name == qn("p:sp") and shape_elm.has_ph_elm:
+def _NotesSlideShapeFactory(shape_elm: ShapeElement, parent: ProvidesPart) -> BaseShape:
+ """Return appropriate shape object for `shape_elm` on a notes slide."""
+ if isinstance(shape_elm, CT_Shape) and shape_elm.has_ph_elm:
return NotesSlidePlaceholder(shape_elm, parent)
return BaseShapeFactory(shape_elm, parent)
-def _SlidePlaceholderFactory(shape_elm, parent):
- """
- Return a placeholder shape of the appropriate type for *shape_elm*.
- """
+def _SlidePlaceholderFactory(shape_elm: ShapeElement, parent: ProvidesPart):
+ """Return a placeholder shape of the appropriate type for `shape_elm`."""
tag = shape_elm.tag
if tag == qn("p:sp"):
Constructor = {
@@ -867,14 +857,11 @@ def _SlidePlaceholderFactory(shape_elm, parent):
Constructor = PlaceholderPicture
else:
Constructor = BaseShapeFactory
- return Constructor(shape_elm, parent)
+ return Constructor(shape_elm, parent) # pyright: ignore[reportArgumentType]
-def SlideShapeFactory(shape_elm, parent):
- """
- Return an instance of the appropriate shape proxy class for *shape_elm*
- on a slide.
- """
+def SlideShapeFactory(shape_elm: ShapeElement, parent: ProvidesPart) -> BaseShape:
+ """Return appropriate shape object for `shape_elm` on a slide."""
if shape_elm.has_ph_elm:
return _SlidePlaceholderFactory(shape_elm, parent)
return BaseShapeFactory(shape_elm, parent)
@@ -883,14 +870,24 @@ def SlideShapeFactory(shape_elm, parent):
class _MoviePicElementCreator(object):
"""Functional service object for creating a new movie p:pic element.
- It's entire external interface is its :meth:`new_movie_pic` class method
- that returns a new `p:pic` element containing the specified video. This
- class is not intended to be constructed or an instance of it retained by
- the caller; it is a "one-shot" object, really a function wrapped in
- a object such that its helper methods can be organized here.
+ It's entire external interface is its :meth:`new_movie_pic` class method that returns a new
+ `p:pic` element containing the specified video. This class is not intended to be constructed
+ or an instance of it retained by the caller; it is a "one-shot" object, really a function
+ wrapped in a object such that its helper methods can be organized here.
"""
- def __init__(self, shapes, shape_id, movie_file, x, y, cx, cy, poster_frame_file, mime_type):
+ def __init__(
+ self,
+ shapes: SlideShapes,
+ shape_id: int,
+ movie_file: str | IO[bytes],
+ x: Length,
+ y: Length,
+ cx: Length,
+ cy: Length,
+ poster_frame_file: str | IO[bytes] | None,
+ mime_type: str | None,
+ ):
super(_MoviePicElementCreator, self).__init__()
self._shapes = shapes
self._shape_id = shape_id
@@ -901,28 +898,35 @@ def __init__(self, shapes, shape_id, movie_file, x, y, cx, cy, poster_frame_file
@classmethod
def new_movie_pic(
- cls, shapes, shape_id, movie_file, x, y, cx, cy, poster_frame_image, mime_type
- ):
- """Return a new `p:pic` element containing video in *movie_file*.
-
- If *mime_type* is None, 'video/unknown' is used. If
- *poster_frame_file* is None, the default "media loudspeaker" image is
- used.
+ cls,
+ shapes: SlideShapes,
+ shape_id: int,
+ movie_file: str | IO[bytes],
+ x: Length,
+ y: Length,
+ cx: Length,
+ cy: Length,
+ poster_frame_image: str | IO[bytes] | None,
+ mime_type: str | None,
+ ) -> CT_Picture:
+ """Return a new `p:pic` element containing video in `movie_file`.
+
+ If `mime_type` is None, 'video/unknown' is used. If `poster_frame_file` is None, the
+ default "media loudspeaker" image is used.
"""
return cls(shapes, shape_id, movie_file, x, y, cx, cy, poster_frame_image, mime_type)._pic
- return
@property
- def _media_rId(self):
+ def _media_rId(self) -> str:
"""Return the rId of RT.MEDIA relationship to video part.
- For historical reasons, there are two relationships to the same part;
- one is the video rId and the other is the media rId.
+ For historical reasons, there are two relationships to the same part; one is the video rId
+ and the other is the media rId.
"""
return self._video_part_rIds[0]
@lazyproperty
- def _pic(self):
+ def _pic(self) -> CT_Picture:
"""Return the new `p:pic` element referencing the video."""
return CT_Picture.new_video_pic(
self._shape_id,
@@ -937,29 +941,27 @@ def _pic(self):
)
@lazyproperty
- def _poster_frame_image_file(self):
+ def _poster_frame_image_file(self) -> str | IO[bytes]:
"""Return the image file for video placeholder image.
- If no poster frame file is provided, the default "media loudspeaker"
- image is used.
+ If no poster frame file is provided, the default "media loudspeaker" image is used.
"""
poster_frame_file = self._poster_frame_file
if poster_frame_file is None:
- return BytesIO(SPEAKER_IMAGE_BYTES)
+ return io.BytesIO(SPEAKER_IMAGE_BYTES)
return poster_frame_file
@lazyproperty
- def _poster_frame_rId(self):
+ def _poster_frame_rId(self) -> str:
"""Return the rId of relationship to poster frame image.
- The poster frame is the image used to represent the video before it's
- played.
+ The poster frame is the image used to represent the video before it's played.
"""
_, poster_frame_rId = self._slide_part.get_or_add_image_part(self._poster_frame_image_file)
return poster_frame_rId
@property
- def _shape_name(self):
+ def _shape_name(self) -> str:
"""Return the appropriate shape name for the p:pic shape.
A movie shape is named with the base filename of the video.
@@ -967,31 +969,30 @@ def _shape_name(self):
return self._video.filename
@property
- def _slide_part(self):
+ def _slide_part(self) -> SlidePart:
"""Return SlidePart object for slide containing this movie."""
return self._shapes.part
@lazyproperty
- def _video(self):
+ def _video(self) -> Video:
"""Return a |Video| object containing the movie file."""
return Video.from_path_or_file_like(self._movie_file, self._mime_type)
@lazyproperty
- def _video_part_rIds(self):
+ def _video_part_rIds(self) -> tuple[str, str]:
"""Return the rIds for relationships to media part for video.
- This is where the media part and its relationships to the slide are
- actually created.
+ This is where the media part and its relationships to the slide are actually created.
"""
media_rId, video_rId = self._slide_part.get_or_add_video_media_part(self._video)
return media_rId, video_rId
@property
- def _video_rId(self):
+ def _video_rId(self) -> str:
"""Return the rId of RT.VIDEO relationship to video part.
- For historical reasons, there are two relationships to the same part;
- one is the video rId and the other is the media rId.
+ For historical reasons, there are two relationships to the same part; one is the video rId
+ and the other is the media rId.
"""
return self._video_part_rIds[1]
@@ -999,26 +1000,26 @@ def _video_rId(self):
class _OleObjectElementCreator(object):
"""Functional service object for creating a new OLE-object p:graphicFrame element.
- It's entire external interface is its :meth:`graphicFrame` class method that returns
- a new `p:graphicFrame` element containing the specified embedded OLE-object shape.
- This class is not intended to be constructed or an instance of it retained by the
- caller; it is a "one-shot" object, really a function wrapped in a object such that
- its helper methods can be organized here.
+ It's entire external interface is its :meth:`graphicFrame` class method that returns a new
+ `p:graphicFrame` element containing the specified embedded OLE-object shape. This class is not
+ intended to be constructed or an instance of it retained by the caller; it is a "one-shot"
+ object, really a function wrapped in a object such that its helper methods can be organized
+ here.
"""
def __init__(
self,
- shapes,
- shape_id,
- ole_object_file,
- prog_id,
- x,
- y,
- cx,
- cy,
- icon_file,
- icon_width,
- icon_height,
+ shapes: _BaseGroupShapes,
+ shape_id: int,
+ ole_object_file: str | IO[bytes],
+ prog_id: PROG_ID | str,
+ x: Length,
+ y: Length,
+ cx: Length | None,
+ cy: Length | None,
+ icon_file: str | IO[bytes] | None,
+ icon_width: Length | None,
+ icon_height: Length | None,
):
self._shapes = shapes
self._shape_id = shape_id
@@ -1035,18 +1036,18 @@ def __init__(
@classmethod
def graphicFrame(
cls,
- shapes,
- shape_id,
- ole_object_file,
- prog_id,
- x,
- y,
- cx,
- cy,
- icon_file,
- icon_width,
- icon_height,
- ):
+ shapes: _BaseGroupShapes,
+ shape_id: int,
+ ole_object_file: str | IO[bytes],
+ prog_id: PROG_ID | str,
+ x: Length,
+ y: Length,
+ cx: Length | None,
+ cy: Length | None,
+ icon_file: str | IO[bytes] | None,
+ icon_width: Length | None,
+ icon_height: Length | None,
+ ) -> CT_GraphicalObjectFrame:
"""Return new `p:graphicFrame` element containing embedded `ole_object_file`."""
return cls(
shapes,
@@ -1063,7 +1064,7 @@ def graphicFrame(
)._graphicFrame
@lazyproperty
- def _graphicFrame(self):
+ def _graphicFrame(self) -> CT_GraphicalObjectFrame:
"""Newly-created `p:graphicFrame` element referencing embedded OLE-object."""
return CT_GraphicalObjectFrame.new_ole_object_graphicFrame(
self._shape_id,
@@ -1080,7 +1081,7 @@ def _graphicFrame(self):
)
@lazyproperty
- def _cx(self):
+ def _cx(self) -> Length:
"""Emu object specifying width of "show-as-icon" image for OLE shape."""
# --- a user-specified width overrides any default ---
if self._cx_arg is not None:
@@ -1093,7 +1094,7 @@ def _cx(self):
)
@lazyproperty
- def _cy(self):
+ def _cy(self) -> Length:
"""Emu object specifying height of "show-as-icon" image for OLE shape."""
# --- a user-specified width overrides any default ---
if self._cy_arg is not None:
@@ -1106,20 +1107,19 @@ def _cy(self):
)
@lazyproperty
- def _icon_height(self):
+ def _icon_height(self) -> Length:
"""Vertical size of enclosed EMF icon within the OLE graphic-frame.
- This must be specified when a custom icon is used, to avoid stretching of the
- image and possible undesired resizing by PowerPoint when the OLE shape is
- double-clicked to open it.
+ This must be specified when a custom icon is used, to avoid stretching of the image and
+ possible undesired resizing by PowerPoint when the OLE shape is double-clicked to open it.
- The correct size can be determined by creating an example PPTX using PowerPoint
- and then inspecting the XML of the OLE graphics-frame (p:oleObj.imgH).
+ The correct size can be determined by creating an example PPTX using PowerPoint and then
+ inspecting the XML of the OLE graphics-frame (p:oleObj.imgH).
"""
return self._icon_height_arg if self._icon_height_arg is not None else Emu(609600)
@lazyproperty
- def _icon_image_file(self):
+ def _icon_image_file(self) -> str | IO[bytes]:
"""Reference to image file containing icon to show in lieu of this object.
This can be either a str path or a file-like object (io.BytesIO typically).
@@ -1140,38 +1140,35 @@ def _icon_image_file(self):
return os.path.abspath(os.path.join(_thisdir, "..", "templates", icon_filename))
@lazyproperty
- def _icon_rId(self):
+ def _icon_rId(self) -> str:
"""str rId like "rId7" of rel to icon (image) representing OLE-object part."""
_, rId = self._slide_part.get_or_add_image_part(self._icon_image_file)
return rId
@lazyproperty
- def _icon_width(self):
+ def _icon_width(self) -> Length:
"""Width of enclosed EMF icon within the OLE graphic-frame.
- This must be specified when a custom icon is used, to avoid stretching of the
- image and possible undesired resizing by PowerPoint when the OLE shape is
- double-clicked to open it.
+ This must be specified when a custom icon is used, to avoid stretching of the image and
+ possible undesired resizing by PowerPoint when the OLE shape is double-clicked to open it.
"""
return self._icon_width_arg if self._icon_width_arg is not None else Emu(965200)
@lazyproperty
- def _ole_object_rId(self):
+ def _ole_object_rId(self) -> str:
"""str rId like "rId6" of relationship to embedded ole_object part.
- This is where the ole_object part and its relationship to the slide are actually
- created.
+ This is where the ole_object part and its relationship to the slide are actually created.
"""
return self._slide_part.add_embedded_ole_object_part(
self._prog_id_arg, self._ole_object_file
)
@lazyproperty
- def _progId(self):
+ def _progId(self) -> str:
"""str like "Excel.Sheet.12" identifying program used to open object.
- This value appears in the `progId` attribute of the `p:oleObj` element for the
- object.
+ This value appears in the `progId` attribute of the `p:oleObj` element for the object.
"""
prog_id_arg = self._prog_id_arg
@@ -1180,7 +1177,7 @@ def _progId(self):
return prog_id_arg.progId if isinstance(prog_id_arg, PROG_ID) else prog_id_arg
@lazyproperty
- def _shape_name(self):
+ def _shape_name(self) -> str:
"""str name like "Object 1" for the embedded ole_object shape.
The name is formed from the prefix "Object " and the shape-id decremented by 1.
@@ -1188,6 +1185,6 @@ def _shape_name(self):
return "Object %d" % (self._shape_id - 1)
@lazyproperty
- def _slide_part(self):
+ def _slide_part(self) -> SlidePart:
"""SlidePart object for this slide."""
return self._shapes.part
diff --git a/src/pptx/shared.py b/src/pptx/shared.py
index 32b529d69..da2a17182 100644
--- a/src/pptx/shared.py
+++ b/src/pptx/shared.py
@@ -1,87 +1,82 @@
-# encoding: utf-8
-
"""Objects shared by pptx modules."""
-from __future__ import unicode_literals
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from pptx.opc.package import XmlPart
+ from pptx.oxml.xmlchemy import BaseOxmlElement
+ from pptx.types import ProvidesPart
class ElementProxy(object):
- """
- Base class for lxml element proxy classes. An element proxy class is one
- whose primary responsibilities are fulfilled by manipulating the
- attributes and child elements of an XML element. They are the most common
- type of class in python-pptx other than custom element (oxml) classes.
+ """Base class for lxml element proxy classes.
+
+ An element proxy class is one whose primary responsibilities are fulfilled by manipulating the
+ attributes and child elements of an XML element. They are the most common type of class in
+ python-pptx other than custom element (oxml) classes.
"""
- def __init__(self, element):
+ def __init__(self, element: BaseOxmlElement):
self._element = element
- def __eq__(self, other):
- """
- Return |True| if this proxy object refers to the same oxml element as
- does *other*. ElementProxy objects are value objects and should
- maintain no mutable local state. Equality for proxy objects is
- defined as referring to the same XML element, whether or not they are
- the same proxy object instance.
+ def __eq__(self, other: object) -> bool:
+ """Return |True| if this proxy object refers to the same oxml element as does *other*.
+
+ ElementProxy objects are value objects and should maintain no mutable local state.
+ Equality for proxy objects is defined as referring to the same XML element, whether or not
+ they are the same proxy object instance.
"""
if not isinstance(other, ElementProxy):
return False
return self._element is other._element
- def __ne__(self, other):
+ def __ne__(self, other: object) -> bool:
if not isinstance(other, ElementProxy):
return True
return self._element is not other._element
@property
def element(self):
- """
- The lxml element proxied by this object.
- """
+ """The lxml element proxied by this object."""
return self._element
class ParentedElementProxy(ElementProxy):
- """
- Provides common services for document elements that occur below a part
- but may occasionally require an ancestor object to provide a service,
- such as add or drop a relationship. Provides the :attr:`_parent`
- attribute to subclasses and the public :attr:`parent` read-only property.
+ """Provides access to ancestor objects and part.
+
+ An ancestor may occasionally be required to provide a service, such as add or drop a
+ relationship. Provides the :attr:`_parent` attribute to subclasses and the public
+ :attr:`parent` read-only property.
"""
- def __init__(self, element, parent):
+ def __init__(self, element: BaseOxmlElement, parent: ProvidesPart):
super(ParentedElementProxy, self).__init__(element)
self._parent = parent
@property
def parent(self):
- """
- The ancestor proxy object to this one. For example, the parent of
- a shape is generally the |SlideShapes| object that contains it.
+ """The ancestor proxy object to this one.
+
+ For example, the parent of a shape is generally the |SlideShapes| object that contains it.
"""
return self._parent
@property
- def part(self):
- """
- The package part containing this object
- """
+ def part(self) -> XmlPart:
+ """The package part containing this object."""
return self._parent.part
class PartElementProxy(ElementProxy):
- """
- Provides common members for proxy objects that wrap the root element of
- a part such as `p:sld`.
- """
+ """Provides common members for proxy-objects that wrap a part's root element, e.g. `p:sld`."""
- def __init__(self, element, part):
+ def __init__(self, element: BaseOxmlElement, part: XmlPart):
super(PartElementProxy, self).__init__(element)
self._part = part
@property
- def part(self):
- """
- The package part containing this object
- """
+ def part(self) -> XmlPart:
+ """The package part containing this object."""
return self._part
diff --git a/src/pptx/slide.py b/src/pptx/slide.py
index 9b93666c6..3b1b65d8e 100644
--- a/src/pptx/slide.py
+++ b/src/pptx/slide.py
@@ -1,7 +1,9 @@
-# encoding: utf-8
-
"""Slide-related objects, including masters, layouts, and notes."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Iterator, cast
+
from pptx.dml.fill import FillFormat
from pptx.enum.shapes import PP_PLACEHOLDER
from pptx.shapes.shapetree import (
@@ -17,12 +19,30 @@
from pptx.shared import ElementProxy, ParentedElementProxy, PartElementProxy
from pptx.util import lazyproperty
+if TYPE_CHECKING:
+ from pptx.oxml.presentation import CT_SlideIdList, CT_SlideMasterIdList
+ from pptx.oxml.slide import (
+ CT_CommonSlideData,
+ CT_NotesSlide,
+ CT_Slide,
+ CT_SlideLayoutIdList,
+ CT_SlideMaster,
+ )
+ from pptx.parts.presentation import PresentationPart
+ from pptx.parts.slide import SlideLayoutPart, SlideMasterPart, SlidePart
+ from pptx.presentation import Presentation
+ from pptx.shapes.placeholder import LayoutPlaceholder, MasterPlaceholder
+ from pptx.shapes.shapetree import NotesSlidePlaceholder
+ from pptx.text.text import TextFrame
+
class _BaseSlide(PartElementProxy):
"""Base class for slide objects, including masters, layouts and notes."""
+ _element: CT_Slide
+
@lazyproperty
- def background(self):
+ def background(self) -> _Background:
"""|_Background| object providing slide background properties.
This property returns a |_Background| object whether or not the
@@ -34,31 +54,31 @@ def background(self):
return _Background(self._element.cSld)
@property
- def name(self):
- """
- String representing the internal name of this slide. Returns an empty
- string (`''`) if no name is assigned. Assigning an empty string or
- |None| to this property causes any name to be removed.
+ def name(self) -> str:
+ """String representing the internal name of this slide.
+
+ Returns an empty string (`''`) if no name is assigned. Assigning an empty string or |None|
+ to this property causes any name to be removed.
"""
return self._element.cSld.name
@name.setter
- def name(self, value):
+ def name(self, value: str | None):
new_value = "" if value is None else value
self._element.cSld.name = new_value
class _BaseMaster(_BaseSlide):
- """
- Base class for master objects such as |SlideMaster| and |NotesMaster|.
+ """Base class for master objects such as |SlideMaster| and |NotesMaster|.
+
Provides access to placeholders and regular shapes.
"""
@lazyproperty
- def placeholders(self):
- """
- Instance of |MasterPlaceholders| containing sequence of placeholder
- shapes in this master, sorted in *idx* order.
+ def placeholders(self) -> MasterPlaceholders:
+ """|MasterPlaceholders| collection of placeholder shapes in this master.
+
+ Sequence sorted in `idx` order.
"""
return MasterPlaceholders(self._element.spTree, self)
@@ -72,9 +92,9 @@ def shapes(self):
class NotesMaster(_BaseMaster):
- """
- Proxy for the notes master XML document. Provides access to shapes, the
- most commonly used of which are placeholders.
+ """Proxy for the notes master XML document.
+
+ Provides access to shapes, the most commonly used of which are placeholders.
"""
@@ -85,19 +105,21 @@ class NotesSlide(_BaseSlide):
page.
"""
- def clone_master_placeholders(self, notes_master):
- """Selectively add placeholder shape elements from *notes_master*.
+ element: CT_NotesSlide # pyright: ignore[reportIncompatibleMethodOverride]
+
+ def clone_master_placeholders(self, notes_master: NotesMaster) -> None:
+ """Selectively add placeholder shape elements from `notes_master`.
- Selected placeholder shape elements from *notes_master* are added to the shapes
+ Selected placeholder shape elements from `notes_master` are added to the shapes
collection of this notes slide. Z-order of placeholders is preserved. Certain
placeholders (header, date, footer) are not cloned.
"""
- def iter_cloneable_placeholders(notes_master):
- """
- Generate a reference to each placeholder in *notes_master* that
- should be cloned to a notes slide when the a new notes slide is
- created.
+ def iter_cloneable_placeholders() -> Iterator[MasterPlaceholder]:
+ """Generate a reference to each cloneable placeholder in `notes_master`.
+
+ These are the placeholders that should be cloned to a notes slide when the a new notes
+ slide is created.
"""
cloneable = (
PP_PLACEHOLDER.SLIDE_IMAGE,
@@ -109,17 +131,16 @@ def iter_cloneable_placeholders(notes_master):
yield placeholder
shapes = self.shapes
- for placeholder in iter_cloneable_placeholders(notes_master):
- shapes.clone_placeholder(placeholder)
+ for placeholder in iter_cloneable_placeholders():
+ shapes.clone_placeholder(cast("LayoutPlaceholder", placeholder))
@property
- def notes_placeholder(self):
- """
- Return the notes placeholder on this notes slide, the shape that
- contains the actual notes text. Return |None| if no notes placeholder
- is present; while this is probably uncommon, it can happen if the
- notes master does not have a body placeholder, or if the notes
- placeholder has been deleted from the notes slide.
+ def notes_placeholder(self) -> NotesSlidePlaceholder | None:
+ """the notes placeholder on this notes slide, the shape that contains the actual notes text.
+
+ Return |None| if no notes placeholder is present; while this is probably uncommon, it can
+ happen if the notes master does not have a body placeholder, or if the notes placeholder
+ has been deleted from the notes slide.
"""
for placeholder in self.placeholders:
if placeholder.placeholder_format.type == PP_PLACEHOLDER.BODY:
@@ -127,12 +148,11 @@ def notes_placeholder(self):
return None
@property
- def notes_text_frame(self):
- """
- Return the text frame of the notes placeholder on this notes slide,
- or |None| if there is no notes placeholder. This is a shortcut to
- accommodate the common case of simply adding "notes" text to the
- notes "page".
+ def notes_text_frame(self) -> TextFrame | None:
+ """The text frame of the notes placeholder on this notes slide.
+
+ |None| if there is no notes placeholder. This is a shortcut to accommodate the common case
+ of simply adding "notes" text to the notes "page".
"""
notes_placeholder = self.notes_placeholder
if notes_placeholder is None:
@@ -140,38 +160,23 @@ def notes_text_frame(self):
return notes_placeholder.text_frame
@lazyproperty
- def placeholders(self):
- """
- An instance of |NotesSlidePlaceholders| containing the sequence of
- placeholder shapes in this notes slide.
+ def placeholders(self) -> NotesSlidePlaceholders:
+ """Instance of |NotesSlidePlaceholders| for this notes-slide.
+
+ Contains the sequence of placeholder shapes in this notes slide.
"""
return NotesSlidePlaceholders(self.element.spTree, self)
@lazyproperty
- def shapes(self):
- """
- An instance of |NotesSlideShapes| containing the sequence of shape
- objects appearing on this notes slide.
- """
+ def shapes(self) -> NotesSlideShapes:
+ """Sequence of shape objects appearing on this notes slide."""
return NotesSlideShapes(self._element.spTree, self)
class Slide(_BaseSlide):
"""Slide object. Provides access to shapes and slide-level properties."""
- @property
- def background(self):
- """|_Background| object providing slide background properties.
-
- This property returns a |_Background| object whether or not the slide
- overrides the default background or inherits it. Determining which of
- those conditions applies for this slide is accomplished using the
- :attr:`follow_master_background` property.
-
- The same |_Background| object is returned on every call for the same
- slide object.
- """
- return super(Slide, self).background
+ part: SlidePart # pyright: ignore[reportIncompatibleMethodOverride]
@property
def follow_master_background(self):
@@ -188,115 +193,99 @@ def follow_master_background(self):
return self._element.bg is None
@property
- def has_notes_slide(self):
- """
- Return True if this slide has a notes slide, False otherwise. A notes
- slide is created by :attr:`.notes_slide` when one doesn't exist; use
- this property to test for a notes slide without the possible side
- effect of creating one.
+ def has_notes_slide(self) -> bool:
+ """`True` if this slide has a notes slide, `False` otherwise.
+
+ A notes slide is created by :attr:`.notes_slide` when one doesn't exist; use this property
+ to test for a notes slide without the possible side effect of creating one.
"""
return self.part.has_notes_slide
@property
- def notes_slide(self):
- """
- Return the |NotesSlide| instance for this slide. If the slide does
- not have a notes slide, one is created. The same single instance is
+ def notes_slide(self) -> NotesSlide:
+ """The |NotesSlide| instance for this slide.
+
+ If the slide does not have a notes slide, one is created. The same single instance is
returned on each call.
"""
return self.part.notes_slide
@lazyproperty
- def placeholders(self):
- """
- Instance of |SlidePlaceholders| containing sequence of placeholder
- shapes in this slide.
- """
+ def placeholders(self) -> SlidePlaceholders:
+ """Sequence of placeholder shapes in this slide."""
return SlidePlaceholders(self._element.spTree, self)
@lazyproperty
- def shapes(self):
- """
- Instance of |SlideShapes| containing sequence of shape objects
- appearing on this slide.
- """
+ def shapes(self) -> SlideShapes:
+ """Sequence of shape objects appearing on this slide."""
return SlideShapes(self._element.spTree, self)
@property
- def slide_id(self):
- """
- The integer value that uniquely identifies this slide within this
- presentation. The slide id does not change if the position of this
- slide in the slide sequence is changed by adding, rearranging, or
- deleting slides.
+ def slide_id(self) -> int:
+ """Integer value that uniquely identifies this slide within this presentation.
+
+ The slide id does not change if the position of this slide in the slide sequence is changed
+ by adding, rearranging, or deleting slides.
"""
return self.part.slide_id
@property
- def slide_layout(self):
- """
- |SlideLayout| object this slide inherits appearance from.
- """
+ def slide_layout(self) -> SlideLayout:
+ """|SlideLayout| object this slide inherits appearance from."""
return self.part.slide_layout
class Slides(ParentedElementProxy):
+ """Sequence of slides belonging to an instance of |Presentation|.
+
+ Has list semantics for access to individual slides. Supports indexed access, len(), and
+ iteration.
"""
- Sequence of slides belonging to an instance of |Presentation|, having
- list semantics for access to individual slides. Supports indexed access,
- len(), and iteration.
- """
- def __init__(self, sldIdLst, prs):
+ part: PresentationPart # pyright: ignore[reportIncompatibleMethodOverride]
+
+ def __init__(self, sldIdLst: CT_SlideIdList, prs: Presentation):
super(Slides, self).__init__(sldIdLst, prs)
self._sldIdLst = sldIdLst
- def __getitem__(self, idx):
- """
- Provide indexed access, (e.g. 'slides[0]').
- """
+ def __getitem__(self, idx: int) -> Slide:
+ """Provide indexed access, (e.g. 'slides[0]')."""
try:
- sldId = self._sldIdLst[idx]
+ sldId = self._sldIdLst.sldId_lst[idx]
except IndexError:
raise IndexError("slide index out of range")
return self.part.related_slide(sldId.rId)
- def __iter__(self):
- """
- Support iteration (e.g. 'for slide in slides:').
- """
- for sldId in self._sldIdLst:
+ def __iter__(self) -> Iterator[Slide]:
+ """Support iteration, e.g. `for slide in slides:`."""
+ for sldId in self._sldIdLst.sldId_lst:
yield self.part.related_slide(sldId.rId)
- def __len__(self):
- """
- Support len() built-in function (e.g. 'len(slides) == 4').
- """
+ def __len__(self) -> int:
+ """Support len() built-in function, e.g. `len(slides) == 4`."""
return len(self._sldIdLst)
- def add_slide(self, slide_layout):
- """
- Return a newly added slide that inherits layout from *slide_layout*.
- """
+ def add_slide(self, slide_layout: SlideLayout) -> Slide:
+ """Return a newly added slide that inherits layout from `slide_layout`."""
rId, slide = self.part.add_slide(slide_layout)
slide.shapes.clone_layout_placeholders(slide_layout)
self._sldIdLst.add_sldId(rId)
return slide
- def get(self, slide_id, default=None):
- """
- Return the slide identified by integer *slide_id* in this
- presentation, or *default* if not found.
+ def get(self, slide_id: int, default: Slide | None = None) -> Slide | None:
+ """Return the slide identified by int `slide_id` in this presentation.
+
+ Returns `default` if not found.
"""
slide = self.part.get_slide(slide_id)
if slide is None:
return default
return slide
- def index(self, slide):
- """
- Map *slide* to an integer representing its zero-based position in
- this slide collection. Raises |ValueError| on *slide* not present.
+ def index(self, slide: Slide) -> int:
+ """Map `slide` to its zero-based position in this slide sequence.
+
+ Raises |ValueError| on *slide* not present.
"""
for idx, this_slide in enumerate(self):
if this_slide == slide:
@@ -305,16 +294,17 @@ def index(self, slide):
class SlideLayout(_BaseSlide):
- """
- Slide layout object. Provides access to placeholders, regular shapes, and
- slide layout-level properties.
+ """Slide layout object.
+
+ Provides access to placeholders, regular shapes, and slide layout-level properties.
"""
- def iter_cloneable_placeholders(self):
- """
- Generate a reference to each layout placeholder on this slide layout
- that should be cloned to a slide when the layout is applied to that
- slide.
+ part: SlideLayoutPart # pyright: ignore[reportIncompatibleMethodOverride]
+
+ def iter_cloneable_placeholders(self) -> Iterator[LayoutPlaceholder]:
+ """Generate layout-placeholders on this slide-layout that should be cloned to a new slide.
+
+ Used when creating a new slide from this slide-layout.
"""
latent_ph_types = (
PP_PLACEHOLDER.DATE,
@@ -326,26 +316,21 @@ def iter_cloneable_placeholders(self):
yield ph
@lazyproperty
- def placeholders(self):
- """
- Instance of |LayoutPlaceholders| containing sequence of placeholder
- shapes in this slide layout, sorted in *idx* order.
+ def placeholders(self) -> LayoutPlaceholders:
+ """Sequence of placeholder shapes in this slide layout.
+
+ Placeholders appear in `idx` order.
"""
return LayoutPlaceholders(self._element.spTree, self)
@lazyproperty
- def shapes(self):
- """
- Instance of |LayoutShapes| containing the sequence of shapes
- appearing on this slide layout.
- """
+ def shapes(self) -> LayoutShapes:
+ """Sequence of shapes appearing on this slide layout."""
return LayoutShapes(self._element.spTree, self)
@property
- def slide_master(self):
- """
- Slide master from which this slide layout inherits properties.
- """
+ def slide_master(self) -> SlideMaster:
+ """Slide master from which this slide-layout inherits properties."""
return self.part.slide_master
@property
@@ -362,56 +347,51 @@ class SlideLayouts(ParentedElementProxy):
Supports indexed access, len(), iteration, index() and remove().
"""
- def __init__(self, sldLayoutIdLst, parent):
+ part: SlideMasterPart # pyright: ignore[reportIncompatibleMethodOverride]
+
+ def __init__(self, sldLayoutIdLst: CT_SlideLayoutIdList, parent: SlideMaster):
super(SlideLayouts, self).__init__(sldLayoutIdLst, parent)
self._sldLayoutIdLst = sldLayoutIdLst
- def __getitem__(self, idx):
- """
- Provide indexed access, (e.g. ``slide_layouts[2]``).
- """
+ def __getitem__(self, idx: int) -> SlideLayout:
+ """Provides indexed access, e.g. `slide_layouts[2]`."""
try:
- sldLayoutId = self._sldLayoutIdLst[idx]
+ sldLayoutId = self._sldLayoutIdLst.sldLayoutId_lst[idx]
except IndexError:
raise IndexError("slide layout index out of range")
return self.part.related_slide_layout(sldLayoutId.rId)
- def __iter__(self):
- """
- Generate a reference to each of the |SlideLayout| instances in the
- collection, in sequence.
- """
- for sldLayoutId in self._sldLayoutIdLst:
+ def __iter__(self) -> Iterator[SlideLayout]:
+ """Generate each |SlideLayout| in the collection, in sequence."""
+ for sldLayoutId in self._sldLayoutIdLst.sldLayoutId_lst:
yield self.part.related_slide_layout(sldLayoutId.rId)
- def __len__(self):
- """
- Support len() built-in function (e.g. 'len(slides) == 4').
- """
+ def __len__(self) -> int:
+ """Support len() built-in function, e.g. `len(slides) == 4`."""
return len(self._sldLayoutIdLst)
- def get_by_name(self, name, default=None):
- """Return SlideLayout object having *name* or *default* if not found."""
+ def get_by_name(self, name: str, default: SlideLayout | None = None) -> SlideLayout | None:
+ """Return SlideLayout object having `name`, or `default` if not found."""
for slide_layout in self:
if slide_layout.name == name:
return slide_layout
return default
- def index(self, slide_layout):
- """Return zero-based index of *slide_layout* in this collection.
+ def index(self, slide_layout: SlideLayout) -> int:
+ """Return zero-based index of `slide_layout` in this collection.
- Raises ValueError if *slide_layout* is not present in this collection.
+ Raises `ValueError` if `slide_layout` is not present in this collection.
"""
for idx, this_layout in enumerate(self):
if slide_layout == this_layout:
return idx
raise ValueError("layout not in this SlideLayouts collection")
- def remove(self, slide_layout):
- """Remove *slide_layout* from the collection.
+ def remove(self, slide_layout: SlideLayout) -> None:
+ """Remove `slide_layout` from the collection.
- Raises ValueError when *slide_layout* is in use; a slide layout which is the
- basis for one or more slides cannot be removed.
+ Raises ValueError when `slide_layout` is in use; a slide layout which is the basis for one
+ or more slides cannot be removed.
"""
# ---raise if layout is in use---
if slide_layout.used_by_slides:
@@ -432,14 +412,16 @@ def remove(self, slide_layout):
class SlideMaster(_BaseMaster):
- """
- Slide master object. Provides access to slide layouts. Access to
- placeholders, regular shapes, and slide master-level properties is
- inherited from |_BaseMaster|.
+ """Slide master object.
+
+ Provides access to slide layouts. Access to placeholders, regular shapes, and slide master-level
+ properties is inherited from |_BaseMaster|.
"""
+ _element: CT_SlideMaster # pyright: ignore[reportIncompatibleVariableOverride]
+
@lazyproperty
- def slide_layouts(self):
+ def slide_layouts(self) -> SlideLayouts:
"""|SlideLayouts| object providing access to this slide-master's layouts."""
return SlideLayouts(self._element.get_or_add_sldLayoutIdLst(), self)
@@ -450,32 +432,27 @@ class SlideMasters(ParentedElementProxy):
Has list access semantics, supporting indexed access, len(), and iteration.
"""
- def __init__(self, sldMasterIdLst, parent):
+ part: PresentationPart # pyright: ignore[reportIncompatibleMethodOverride]
+
+ def __init__(self, sldMasterIdLst: CT_SlideMasterIdList, parent: Presentation):
super(SlideMasters, self).__init__(sldMasterIdLst, parent)
self._sldMasterIdLst = sldMasterIdLst
- def __getitem__(self, idx):
- """
- Provide indexed access, (e.g. ``slide_masters[2]``).
- """
+ def __getitem__(self, idx: int) -> SlideMaster:
+ """Provides indexed access, e.g. `slide_masters[2]`."""
try:
- sldMasterId = self._sldMasterIdLst[idx]
+ sldMasterId = self._sldMasterIdLst.sldMasterId_lst[idx]
except IndexError:
raise IndexError("slide master index out of range")
return self.part.related_slide_master(sldMasterId.rId)
def __iter__(self):
- """
- Generate a reference to each of the |SlideMaster| instances in the
- collection, in sequence.
- """
- for smi in self._sldMasterIdLst:
+ """Generate each |SlideMaster| instance in the collection, in sequence."""
+ for smi in self._sldMasterIdLst.sldMasterId_lst:
yield self.part.related_slide_master(smi.rId)
def __len__(self):
- """
- Support len() built-in function (e.g. 'len(slide_masters) == 4').
- """
+ """Support len() built-in function, e.g. `len(slide_masters) == 4`."""
return len(self._sldMasterIdLst)
@@ -487,7 +464,7 @@ class _Background(ElementProxy):
has a |_Background| object.
"""
- def __init__(self, cSld):
+ def __init__(self, cSld: CT_CommonSlideData):
super(_Background, self).__init__(cSld)
self._cSld = cSld
diff --git a/src/pptx/spec.py b/src/pptx/spec.py
index 835fde6d0..1e7bffb36 100644
--- a/src/pptx/spec.py
+++ b/src/pptx/spec.py
@@ -1,23 +1,34 @@
-# encoding: utf-8
-
"""Mappings from the ISO/IEC 29500 spec.
Some of these are inferred from PowerPoint application behavior
"""
-from pptx.enum.shapes import MSO_SHAPE
+from __future__ import annotations
+from typing import TYPE_CHECKING, TypedDict
+
+from pptx.enum.shapes import MSO_SHAPE
GRAPHIC_DATA_URI_CHART = "http://schemas.openxmlformats.org/drawingml/2006/chart"
GRAPHIC_DATA_URI_OLEOBJ = "http://schemas.openxmlformats.org/presentationml/2006/ole"
GRAPHIC_DATA_URI_TABLE = "http://schemas.openxmlformats.org/drawingml/2006/table"
+if TYPE_CHECKING:
+ from typing_extensions import TypeAlias
+
+AdjustmentValue: TypeAlias = tuple[str, int]
+
+
+class ShapeSpec(TypedDict):
+ basename: str
+ avLst: tuple[AdjustmentValue, ...]
+
# ============================================================================
# AutoShape type specs
# ============================================================================
-autoshape_types = {
+autoshape_types: dict[MSO_SHAPE, ShapeSpec] = {
MSO_SHAPE.ACTION_BUTTON_BACK_OR_PREVIOUS: {
"basename": "Action Button: Back or Previous",
"avLst": (),
diff --git a/src/pptx/table.py b/src/pptx/table.py
index 63872eab8..3bdf54ba6 100644
--- a/src/pptx/table.py
+++ b/src/pptx/table.py
@@ -1,13 +1,22 @@
-# encoding: utf-8
-
"""Table-related objects such as Table and Cell."""
-from pptx.compat import is_integer
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Iterator
+
from pptx.dml.fill import FillFormat
from pptx.oxml.table import TcRange
from pptx.shapes import Subshape
from pptx.text.text import TextFrame
-from pptx.util import lazyproperty
+from pptx.util import Emu, lazyproperty
+
+if TYPE_CHECKING:
+ from pptx.enum.text import MSO_VERTICAL_ANCHOR
+ from pptx.oxml.table import CT_Table, CT_TableCell, CT_TableCol, CT_TableRow
+ from pptx.parts.slide import BaseSlidePart
+ from pptx.shapes.graphfrm import GraphicFrame
+ from pptx.types import ProvidesPart
+ from pptx.util import Length
class Table(object):
@@ -17,66 +26,68 @@ class Table(object):
:meth:`.Slide.shapes.add_table` to add a table to a slide.
"""
- def __init__(self, tbl, graphic_frame):
+ def __init__(self, tbl: CT_Table, graphic_frame: GraphicFrame):
super(Table, self).__init__()
self._tbl = tbl
self._graphic_frame = graphic_frame
- def cell(self, row_idx, col_idx):
- """Return cell at *row_idx*, *col_idx*.
+ def cell(self, row_idx: int, col_idx: int) -> _Cell:
+ """Return cell at `row_idx`, `col_idx`.
- Return value is an instance of |_Cell|. *row_idx* and *col_idx* are
- zero-based, e.g. cell(0, 0) is the top, left cell in the table.
+ Return value is an instance of |_Cell|. `row_idx` and `col_idx` are zero-based, e.g.
+ cell(0, 0) is the top, left cell in the table.
"""
return _Cell(self._tbl.tc(row_idx, col_idx), self)
@lazyproperty
- def columns(self):
+ def columns(self) -> _ColumnCollection:
"""|_ColumnCollection| instance for this table.
- Provides access to |_Column| objects representing the table's columns. |_Column|
- objects are accessed using list notation, e.g. ``col = tbl.columns[0]``.
+ Provides access to |_Column| objects representing the table's columns. |_Column| objects
+ are accessed using list notation, e.g. `col = tbl.columns[0]`.
"""
return _ColumnCollection(self._tbl, self)
@property
- def first_col(self):
- """
- Read/write boolean property which, when true, indicates the first
- column should be formatted differently, as for a side-heading column
- at the far left of the table.
+ def first_col(self) -> bool:
+ """When `True`, indicates first column should have distinct formatting.
+
+ Read/write. Distinct formatting is used, for example, when the first column contains row
+ headings (is a side-heading column).
"""
return self._tbl.firstCol
@first_col.setter
- def first_col(self, value):
+ def first_col(self, value: bool):
self._tbl.firstCol = value
@property
- def first_row(self):
- """
- Read/write boolean property which, when true, indicates the first
- row should be formatted differently, e.g. for column headings.
+ def first_row(self) -> bool:
+ """When `True`, indicates first row should have distinct formatting.
+
+ Read/write. Distinct formatting is used, for example, when the first row contains column
+ headings.
"""
return self._tbl.firstRow
@first_row.setter
- def first_row(self, value):
+ def first_row(self, value: bool):
self._tbl.firstRow = value
@property
- def horz_banding(self):
- """
- Read/write boolean property which, when true, indicates the rows of
- the table should appear with alternating shading.
+ def horz_banding(self) -> bool:
+ """When `True`, indicates rows should have alternating shading.
+
+ Read/write. Used to allow rows to be traversed more easily without losing track of which
+ row is being read.
"""
return self._tbl.bandRow
@horz_banding.setter
- def horz_banding(self, value):
+ def horz_banding(self, value: bool):
self._tbl.bandRow = value
- def iter_cells(self):
+ def iter_cells(self) -> Iterator[_Cell]:
"""Generate _Cell object for each cell in this table.
Each grid cell is generated in left-to-right, top-to-bottom order.
@@ -84,185 +95,177 @@ def iter_cells(self):
return (_Cell(tc, self) for tc in self._tbl.iter_tcs())
@property
- def last_col(self):
- """
- Read/write boolean property which, when true, indicates the last
- column should be formatted differently, as for a row totals column at
- the far right of the table.
+ def last_col(self) -> bool:
+ """When `True`, indicates the rightmost column should have distinct formatting.
+
+ Read/write. Used, for example, when a row totals column appears at the far right of the
+ table.
"""
return self._tbl.lastCol
@last_col.setter
- def last_col(self, value):
+ def last_col(self, value: bool):
self._tbl.lastCol = value
@property
- def last_row(self):
- """
- Read/write boolean property which, when true, indicates the last
- row should be formatted differently, as for a totals row at the
- bottom of the table.
+ def last_row(self) -> bool:
+ """When `True`, indicates the bottom row should have distinct formatting.
+
+ Read/write. Used, for example, when a totals row appears as the bottom row.
"""
return self._tbl.lastRow
@last_row.setter
- def last_row(self, value):
+ def last_row(self, value: bool):
self._tbl.lastRow = value
- def notify_height_changed(self):
- """
- Called by a row when its height changes, triggering the graphic frame
- to recalculate its total height (as the sum of the row heights).
+ def notify_height_changed(self) -> None:
+ """Called by a row when its height changes.
+
+ Triggers the graphic frame to recalculate its total height (as the sum of the row
+ heights).
"""
- new_table_height = sum([row.height for row in self.rows])
+ new_table_height = Emu(sum([row.height for row in self.rows]))
self._graphic_frame.height = new_table_height
- def notify_width_changed(self):
- """
- Called by a column when its width changes, triggering the graphic
- frame to recalculate its total width (as the sum of the column
+ def notify_width_changed(self) -> None:
+ """Called by a column when its width changes.
+
+ Triggers the graphic frame to recalculate its total width (as the sum of the column
widths).
"""
- new_table_width = sum([col.width for col in self.columns])
+ new_table_width = Emu(sum([col.width for col in self.columns]))
self._graphic_frame.width = new_table_width
@property
- def part(self):
- """
- The package part containing this table.
- """
+ def part(self) -> BaseSlidePart:
+ """The package part containing this table."""
return self._graphic_frame.part
@lazyproperty
def rows(self):
"""|_RowCollection| instance for this table.
- Provides access to |_Row| objects representing the table's rows. |_Row| objects
- are accessed using list notation, e.g. ``col = tbl.rows[0]``.
+ Provides access to |_Row| objects representing the table's rows. |_Row| objects are
+ accessed using list notation, e.g. `col = tbl.rows[0]`.
"""
return _RowCollection(self._tbl, self)
@property
- def vert_banding(self):
- """
- Read/write boolean property which, when true, indicates the columns
- of the table should appear with alternating shading.
+ def vert_banding(self) -> bool:
+ """When `True`, indicates columns should have alternating shading.
+
+ Read/write. Used to allow columns to be traversed more easily without losing track of
+ which column is being read.
"""
return self._tbl.bandCol
@vert_banding.setter
- def vert_banding(self, value):
+ def vert_banding(self, value: bool):
self._tbl.bandCol = value
class _Cell(Subshape):
"""Table cell"""
- def __init__(self, tc, parent):
+ def __init__(self, tc: CT_TableCell, parent: ProvidesPart):
super(_Cell, self).__init__(parent)
self._tc = tc
- def __eq__(self, other):
- """|True| if this object proxies the same element as *other*.
+ def __eq__(self, other: object) -> bool:
+ """|True| if this object proxies the same element as `other`.
- Equality for proxy objects is defined as referring to the same XML
- element, whether or not they are the same proxy object instance.
+ Equality for proxy objects is defined as referring to the same XML element, whether or not
+ they are the same proxy object instance.
"""
if not isinstance(other, type(self)):
return False
return self._tc is other._tc
- def __ne__(self, other):
+ def __ne__(self, other: object) -> bool:
if not isinstance(other, type(self)):
return True
return self._tc is not other._tc
@lazyproperty
- def fill(self):
- """
- |FillFormat| instance for this cell, providing access to fill
- properties such as foreground color.
+ def fill(self) -> FillFormat:
+ """|FillFormat| instance for this cell.
+
+ Provides access to fill properties such as foreground color.
"""
tcPr = self._tc.get_or_add_tcPr()
return FillFormat.from_fill_parent(tcPr)
@property
- def is_merge_origin(self):
+ def is_merge_origin(self) -> bool:
"""True if this cell is the top-left grid cell in a merged cell."""
return self._tc.is_merge_origin
@property
- def is_spanned(self):
+ def is_spanned(self) -> bool:
"""True if this cell is spanned by a merge-origin cell.
- A merge-origin cell "spans" the other grid cells in its merge range,
- consuming their area and "shadowing" the spanned grid cells.
+ A merge-origin cell "spans" the other grid cells in its merge range, consuming their area
+ and "shadowing" the spanned grid cells.
- Note this value is |False| for a merge-origin cell. A merge-origin
- cell spans other grid cells, but is not itself a spanned cell.
+ Note this value is |False| for a merge-origin cell. A merge-origin cell spans other grid
+ cells, but is not itself a spanned cell.
"""
return self._tc.is_spanned
@property
- def margin_left(self):
- """
- Read/write integer value of left margin of cell as a |Length| value
- object. If assigned |None|, the default value is used, 0.1 inches for
- left and right margins and 0.05 inches for top and bottom.
+ def margin_left(self) -> Length:
+ """Left margin of cells.
+
+ Read/write. If assigned |None|, the default value is used, 0.1 inches for left and right
+ margins and 0.05 inches for top and bottom.
"""
return self._tc.marL
@margin_left.setter
- def margin_left(self, margin_left):
+ def margin_left(self, margin_left: Length | None):
self._validate_margin_value(margin_left)
self._tc.marL = margin_left
@property
- def margin_right(self):
- """
- Right margin of cell.
- """
+ def margin_right(self) -> Length:
+ """Right margin of cell."""
return self._tc.marR
@margin_right.setter
- def margin_right(self, margin_right):
+ def margin_right(self, margin_right: Length | None):
self._validate_margin_value(margin_right)
self._tc.marR = margin_right
@property
- def margin_top(self):
- """
- Top margin of cell.
- """
+ def margin_top(self) -> Length:
+ """Top margin of cell."""
return self._tc.marT
@margin_top.setter
- def margin_top(self, margin_top):
+ def margin_top(self, margin_top: Length | None):
self._validate_margin_value(margin_top)
self._tc.marT = margin_top
@property
- def margin_bottom(self):
- """
- Bottom margin of cell.
- """
+ def margin_bottom(self) -> Length:
+ """Bottom margin of cell."""
return self._tc.marB
@margin_bottom.setter
- def margin_bottom(self, margin_bottom):
+ def margin_bottom(self, margin_bottom: Length | None):
self._validate_margin_value(margin_bottom)
self._tc.marB = margin_bottom
- def merge(self, other_cell):
- """Create merged cell from this cell to *other_cell*.
+ def merge(self, other_cell: _Cell) -> None:
+ """Create merged cell from this cell to `other_cell`.
- This cell and *other_cell* specify opposite corners of the merged
- cell range. Either diagonal of the cell region may be specified in
- either order, e.g. self=bottom-right, other_cell=top-left, etc.
+ This cell and `other_cell` specify opposite corners of the merged cell range. Either
+ diagonal of the cell region may be specified in either order, e.g. self=bottom-right,
+ other_cell=top-left, etc.
- Raises |ValueError| if the specified range already contains merged
- cells anywhere within its extents or if *other_cell* is not in the
- same table as *self*.
+ Raises |ValueError| if the specified range already contains merged cells anywhere within
+ its extents or if `other_cell` is not in the same table as `self`.
"""
tc_range = TcRange(self._tc, other_cell._tc)
@@ -285,43 +288,38 @@ def merge(self, other_cell):
tc.vMerge = True
@property
- def span_height(self):
+ def span_height(self) -> int:
"""int count of rows spanned by this cell.
- The value of this property may be misleading (often 1) on cells where
- `.is_merge_origin` is not |True|, since only a merge-origin cell
- contains complete span information. This property is only intended
- for use on cells known to be a merge origin by testing
+ The value of this property may be misleading (often 1) on cells where `.is_merge_origin`
+ is not |True|, since only a merge-origin cell contains complete span information. This
+ property is only intended for use on cells known to be a merge origin by testing
`.is_merge_origin`.
"""
return self._tc.rowSpan
@property
- def span_width(self):
+ def span_width(self) -> int:
"""int count of columns spanned by this cell.
- The value of this property may be misleading (often 1) on cells where
- `.is_merge_origin` is not |True|, since only a merge-origin cell
- contains complete span information. This property is only intended
- for use on cells known to be a merge origin by testing
+ The value of this property may be misleading (often 1) on cells where `.is_merge_origin`
+ is not |True|, since only a merge-origin cell contains complete span information. This
+ property is only intended for use on cells known to be a merge origin by testing
`.is_merge_origin`.
"""
return self._tc.gridSpan
- def split(self):
+ def split(self) -> None:
"""Remove merge from this (merge-origin) cell.
- The merged cell represented by this object will be "unmerged",
- yielding a separate unmerged cell for each grid cell previously
- spanned by this merge.
+ The merged cell represented by this object will be "unmerged", yielding a separate
+ unmerged cell for each grid cell previously spanned by this merge.
- Raises |ValueError| when this cell is not a merge-origin cell. Test
- with `.is_merge_origin` before calling.
+ Raises |ValueError| when this cell is not a merge-origin cell. Test with
+ `.is_merge_origin` before calling.
"""
if not self.is_merge_origin:
- raise ValueError(
- "not a merge-origin cell; only a merge-origin cell can be sp" "lit"
- )
+ raise ValueError("not a merge-origin cell; only a merge-origin cell can be sp" "lit")
tc_range = TcRange.from_merge_origin(self._tc)
@@ -330,64 +328,52 @@ def split(self):
tc.hMerge = tc.vMerge = False
@property
- def text(self):
- """Unicode (str in Python 3) representation of cell contents.
-
- The returned string will contain a newline character (``"\\n"``) separating each
- paragraph and a vertical-tab (``"\\v"``) character for each line break (soft
- carriage return) in the cell's text.
-
- Assignment to *text* replaces all text currently contained in the cell. A
- newline character (``"\\n"``) in the assigned text causes a new paragraph to be
- started. A vertical-tab (``"\\v"``) character in the assigned text causes
- a line-break (soft carriage-return) to be inserted. (The vertical-tab character
- appears in clipboard text copied from PowerPoint as its encoding of
- line-breaks.)
-
- Either bytes (Python 2 str) or unicode (Python 3 str) can be assigned. Bytes can
- be 7-bit ASCII or UTF-8 encoded 8-bit bytes. Bytes values are converted to
- unicode assuming UTF-8 encoding (which correctly decodes ASCII).
+ def text(self) -> str:
+ """Textual content of cell as a single string.
+
+ The returned string will contain a newline character (`"\\n"`) separating each paragraph
+ and a vertical-tab (`"\\v"`) character for each line break (soft carriage return) in the
+ cell's text.
+
+ Assignment to `text` replaces all text currently contained in the cell. A newline
+ character (`"\\n"`) in the assigned text causes a new paragraph to be started. A
+ vertical-tab (`"\\v"`) character in the assigned text causes a line-break (soft
+ carriage-return) to be inserted. (The vertical-tab character appears in clipboard text
+ copied from PowerPoint as its encoding of line-breaks.)
"""
return self.text_frame.text
@text.setter
- def text(self, text):
+ def text(self, text: str):
self.text_frame.text = text
@property
- def text_frame(self):
- """
- |TextFrame| instance containing the text that appears in the cell.
- """
+ def text_frame(self) -> TextFrame:
+ """|TextFrame| containing the text that appears in the cell."""
txBody = self._tc.get_or_add_txBody()
return TextFrame(txBody, self)
@property
- def vertical_anchor(self):
+ def vertical_anchor(self) -> MSO_VERTICAL_ANCHOR | None:
"""Vertical alignment of this cell.
- This value is a member of the :ref:`MsoVerticalAnchor` enumeration or
- |None|. A value of |None| indicates the cell has no explicitly
- applied vertical anchor setting and its effective value is inherited
- from its style-hierarchy ancestors.
+ This value is a member of the :ref:`MsoVerticalAnchor` enumeration or |None|. A value of
+ |None| indicates the cell has no explicitly applied vertical anchor setting and its
+ effective value is inherited from its style-hierarchy ancestors.
- Assigning |None| to this property causes any explicitly applied
- vertical anchor setting to be cleared and inheritance of its
- effective value to be restored.
+ Assigning |None| to this property causes any explicitly applied vertical anchor setting to
+ be cleared and inheritance of its effective value to be restored.
"""
return self._tc.anchor
@vertical_anchor.setter
- def vertical_anchor(self, mso_anchor_idx):
+ def vertical_anchor(self, mso_anchor_idx: MSO_VERTICAL_ANCHOR | None):
self._tc.anchor = mso_anchor_idx
@staticmethod
- def _validate_margin_value(margin_value):
- """
- Raise ValueError if *margin_value* is not a positive integer value or
- |None|.
- """
- if not is_integer(margin_value) and margin_value is not None:
+ def _validate_margin_value(margin_value: Length | None) -> None:
+ """Raise ValueError if `margin_value` is not a positive integer value or |None|."""
+ if not isinstance(margin_value, int) and margin_value is not None:
tmpl = "margin value must be integer or None, got '%s'"
raise TypeError(tmpl % margin_value)
@@ -395,19 +381,18 @@ def _validate_margin_value(margin_value):
class _Column(Subshape):
"""Table column"""
- def __init__(self, gridCol, parent):
+ def __init__(self, gridCol: CT_TableCol, parent: _ColumnCollection):
super(_Column, self).__init__(parent)
+ self._parent = parent
self._gridCol = gridCol
@property
- def width(self):
- """
- Width of column in EMU.
- """
+ def width(self) -> Length:
+ """Width of column in EMU."""
return self._gridCol.w
@width.setter
- def width(self, width):
+ def width(self, width: Length):
self._gridCol.w = width
self._parent.notify_width_changed()
@@ -415,27 +400,26 @@ def width(self, width):
class _Row(Subshape):
"""Table row"""
- def __init__(self, tr, parent):
+ def __init__(self, tr: CT_TableRow, parent: _RowCollection):
super(_Row, self).__init__(parent)
+ self._parent = parent
self._tr = tr
@property
def cells(self):
- """
- Read-only reference to collection of cells in row. An individual cell
- is referenced using list notation, e.g. ``cell = row.cells[0]``.
+ """Read-only reference to collection of cells in row.
+
+ An individual cell is referenced using list notation, e.g. `cell = row.cells[0]`.
"""
return _CellCollection(self._tr, self)
@property
- def height(self):
- """
- Height of row in EMU.
- """
+ def height(self) -> Length:
+ """Height of row in EMU."""
return self._tr.h
@height.setter
- def height(self, height):
+ def height(self, height: Length):
self._tr.h = height
self._parent.notify_height_changed()
@@ -443,22 +427,23 @@ def height(self, height):
class _CellCollection(Subshape):
"""Horizontal sequence of row cells"""
- def __init__(self, tr, parent):
+ def __init__(self, tr: CT_TableRow, parent: _Row):
super(_CellCollection, self).__init__(parent)
+ self._parent = parent
self._tr = tr
- def __getitem__(self, idx):
+ def __getitem__(self, idx: int) -> _Cell:
"""Provides indexed access, (e.g. 'cells[0]')."""
if idx < 0 or idx >= len(self._tr.tc_lst):
msg = "cell index [%d] out of range" % idx
raise IndexError(msg)
return _Cell(self._tr.tc_lst[idx], self)
- def __iter__(self):
+ def __iter__(self) -> Iterator[_Cell]:
"""Provides iterability."""
return (_Cell(tc, self) for tc in self._tr.tc_lst)
- def __len__(self):
+ def __len__(self) -> int:
"""Supports len() function (e.g. 'len(cells) == 1')."""
return len(self._tr.tc_lst)
@@ -466,56 +451,46 @@ def __len__(self):
class _ColumnCollection(Subshape):
"""Sequence of table columns."""
- def __init__(self, tbl, parent):
+ def __init__(self, tbl: CT_Table, parent: Table):
super(_ColumnCollection, self).__init__(parent)
+ self._parent = parent
self._tbl = tbl
- def __getitem__(self, idx):
- """
- Provides indexed access, (e.g. 'columns[0]').
- """
+ def __getitem__(self, idx: int):
+ """Provides indexed access, (e.g. 'columns[0]')."""
if idx < 0 or idx >= len(self._tbl.tblGrid.gridCol_lst):
msg = "column index [%d] out of range" % idx
raise IndexError(msg)
return _Column(self._tbl.tblGrid.gridCol_lst[idx], self)
def __len__(self):
- """
- Supports len() function (e.g. 'len(columns) == 1').
- """
+ """Supports len() function (e.g. 'len(columns) == 1')."""
return len(self._tbl.tblGrid.gridCol_lst)
def notify_width_changed(self):
- """
- Called by a column when its width changes. Pass along to parent.
- """
+ """Called by a column when its width changes. Pass along to parent."""
self._parent.notify_width_changed()
class _RowCollection(Subshape):
"""Sequence of table rows"""
- def __init__(self, tbl, parent):
+ def __init__(self, tbl: CT_Table, parent: Table):
super(_RowCollection, self).__init__(parent)
+ self._parent = parent
self._tbl = tbl
- def __getitem__(self, idx):
- """
- Provides indexed access, (e.g. 'rows[0]').
- """
+ def __getitem__(self, idx: int) -> _Row:
+ """Provides indexed access, (e.g. 'rows[0]')."""
if idx < 0 or idx >= len(self):
msg = "row index [%d] out of range" % idx
raise IndexError(msg)
return _Row(self._tbl.tr_lst[idx], self)
def __len__(self):
- """
- Supports len() function (e.g. 'len(rows) == 1').
- """
+ """Supports len() function (e.g. 'len(rows) == 1')."""
return len(self._tbl.tr_lst)
def notify_height_changed(self):
- """
- Called by a row when its height changes. Pass along to parent.
- """
+ """Called by a row when its height changes. Pass along to parent."""
self._parent.notify_height_changed()
diff --git a/src/pptx/text/fonts.py b/src/pptx/text/fonts.py
index ebc5b7d49..5ae054a83 100644
--- a/src/pptx/text/fonts.py
+++ b/src/pptx/text/fonts.py
@@ -1,27 +1,24 @@
-# encoding: utf-8
-
"""Objects related to system font file lookup."""
+from __future__ import annotations
+
import os
import sys
-
from struct import calcsize, unpack_from
-from ..util import lazyproperty
+from pptx.util import lazyproperty
class FontFiles(object):
- """
- A class-based singleton serving as a lazy cache for system font details.
- """
+ """A class-based singleton serving as a lazy cache for system font details."""
_font_files = None
@classmethod
- def find(cls, family_name, is_bold, is_italic):
- """
- Return the absolute path to the installed OpenType font having
- *family_name* and the styles *is_bold* and *is_italic*.
+ def find(cls, family_name: str, is_bold: bool, is_italic: bool) -> str:
+ """Return the absolute path to an installed OpenType font.
+
+ File is matched by `family_name` and the styles `is_bold` and `is_italic`.
"""
if cls._font_files is None:
cls._font_files = cls._installed_fonts()
@@ -327,9 +324,7 @@ def _iter_names(self):
table_bytes = self._table_bytes
for idx in range(count):
- platform_id, name_id, name = self._read_name(
- table_bytes, idx, strings_offset
- )
+ platform_id, name_id, name = self._read_name(table_bytes, idx, strings_offset)
if name is None:
continue
yield ((platform_id, name_id), name)
@@ -360,12 +355,8 @@ def _read_name(self, bufr, idx, strings_offset):
`idx` position in `bufr`. `strings_offset` is the index into `bufr` where actual
name strings begin. The returned name is a unicode string.
"""
- platform_id, enc_id, lang_id, name_id, length, str_offset = self._name_header(
- bufr, idx
- )
- name = self._read_name_text(
- bufr, platform_id, enc_id, strings_offset, str_offset, length
- )
+ platform_id, enc_id, lang_id, name_id, length, str_offset = self._name_header(bufr, idx)
+ name = self._read_name_text(bufr, platform_id, enc_id, strings_offset, str_offset, length)
return platform_id, name_id, name
def _read_name_text(
diff --git a/src/pptx/text/layout.py b/src/pptx/text/layout.py
index c230a0ec6..d2b439939 100644
--- a/src/pptx/text/layout.py
+++ b/src/pptx/text/layout.py
@@ -1,21 +1,26 @@
-# encoding: utf-8
-
"""Objects related to layout of rendered text, such as TextFitter."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
from PIL import ImageFont
+if TYPE_CHECKING:
+ from pptx.util import Length
+
class TextFitter(tuple):
- """
- Value object that knows how to fit text into given rectangular extents.
- """
+ """Value object that knows how to fit text into given rectangular extents."""
def __new__(cls, line_source, extents, font_file):
width, height = extents
return tuple.__new__(cls, (line_source, width, height, font_file))
@classmethod
- def best_fit_font_size(cls, text, extents, max_size, font_file):
+ def best_fit_font_size(
+ cls, text: str, extents: tuple[Length, Length], max_size: int, font_file: str
+ ) -> int:
"""Return whole-number best fit point size less than or equal to `max_size`.
The return value is the largest whole-number point size less than or equal to
@@ -294,9 +299,7 @@ class _Fonts(object):
@classmethod
def font(cls, font_path, point_size):
if (font_path, point_size) not in cls.fonts:
- cls.fonts[(font_path, point_size)] = ImageFont.truetype(
- font_path, point_size
- )
+ cls.fonts[(font_path, point_size)] = ImageFont.truetype(font_path, point_size)
return cls.fonts[(font_path, point_size)]
diff --git a/src/pptx/text/text.py b/src/pptx/text/text.py
index ba941230a..e139410c2 100644
--- a/src/pptx/text/text.py
+++ b/src/pptx/text/text.py
@@ -1,30 +1,49 @@
-# encoding: utf-8
-
"""Text-related objects such as TextFrame and Paragraph."""
-from pptx.compat import to_unicode
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Iterator, cast
+
from pptx.dml.fill import FillFormat
from pptx.enum.dml import MSO_FILL
from pptx.enum.lang import MSO_LANGUAGE_ID
-from pptx.enum.text import MSO_AUTO_SIZE, MSO_UNDERLINE
+from pptx.enum.text import MSO_AUTO_SIZE, MSO_UNDERLINE, MSO_VERTICAL_ANCHOR
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.oxml.simpletypes import ST_TextWrappingType
from pptx.shapes import Subshape
from pptx.text.fonts import FontFiles
from pptx.text.layout import TextFitter
-from pptx.util import Centipoints, Emu, lazyproperty, Pt
+from pptx.util import Centipoints, Emu, Length, Pt, lazyproperty
+
+if TYPE_CHECKING:
+ from pptx.dml.color import ColorFormat
+ from pptx.enum.text import (
+ MSO_TEXT_UNDERLINE_TYPE,
+ MSO_VERTICAL_ANCHOR,
+ PP_PARAGRAPH_ALIGNMENT,
+ )
+ from pptx.oxml.action import CT_Hyperlink
+ from pptx.oxml.text import (
+ CT_RegularTextRun,
+ CT_TextBody,
+ CT_TextCharacterProperties,
+ CT_TextParagraph,
+ CT_TextParagraphProperties,
+ )
+ from pptx.types import ProvidesExtents, ProvidesPart
class TextFrame(Subshape):
"""The part of a shape that contains its text.
- Not all shapes have a text frame. Corresponds to the ```` element that can
- appear as a child element of ````. Not intended to be constructed directly.
+ Not all shapes have a text frame. Corresponds to the `p:txBody` element that can
+ appear as a child element of `p:sp`. Not intended to be constructed directly.
"""
- def __init__(self, txBody, parent):
+ def __init__(self, txBody: CT_TextBody, parent: ProvidesPart):
super(TextFrame, self).__init__(parent)
self._element = self._txBody = txBody
+ self._parent = parent
def add_paragraph(self):
"""
@@ -35,18 +54,18 @@ def add_paragraph(self):
return _Paragraph(p, self)
@property
- def auto_size(self):
- """
- The type of automatic resizing that should be used to fit the text of
- this shape within its bounding box when the text would otherwise
- extend beyond the shape boundaries. May be |None|,
- ``MSO_AUTO_SIZE.NONE``, ``MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT``, or
- ``MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE``.
+ def auto_size(self) -> MSO_AUTO_SIZE | None:
+ """Resizing strategy used to fit text within this shape.
+
+ Determins the type of automatic resizing used to fit the text of this shape within its
+ bounding box when the text would otherwise extend beyond the shape boundaries. May be
+ |None|, `MSO_AUTO_SIZE.NONE`, `MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT`, or
+ `MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE`.
"""
return self._bodyPr.autofit
@auto_size.setter
- def auto_size(self, value):
+ def auto_size(self, value: MSO_AUTO_SIZE | None):
self._bodyPr.autofit = value
def clear(self):
@@ -58,145 +77,126 @@ def clear(self):
def fit_text(
self,
- font_family="Calibri",
- max_size=18,
- bold=False,
- italic=False,
- font_file=None,
+ font_family: str = "Calibri",
+ max_size: int = 18,
+ bold: bool = False,
+ italic: bool = False,
+ font_file: str | None = None,
):
"""Fit text-frame text entirely within bounds of its shape.
- Make the text in this text frame fit entirely within the bounds of
- its shape by setting word wrap on and applying the "best-fit" font
- size to all the text it contains. :attr:`TextFrame.auto_size` is set
- to :attr:`MSO_AUTO_SIZE.NONE`. The font size will not be set larger
- than *max_size* points. If the path to a matching TrueType font is
- provided as *font_file*, that font file will be used for the font
- metrics. If *font_file* is |None|, best efforts are made to locate
- a font file with matchhing *font_family*, *bold*, and *italic*
- installed on the current system (usually succeeds if the font is
- installed).
+ Make the text in this text frame fit entirely within the bounds of its shape by setting
+ word wrap on and applying the "best-fit" font size to all the text it contains.
+
+ :attr:`TextFrame.auto_size` is set to :attr:`MSO_AUTO_SIZE.NONE`. The font size will not
+ be set larger than `max_size` points. If the path to a matching TrueType font is provided
+ as `font_file`, that font file will be used for the font metrics. If `font_file` is |None|,
+ best efforts are made to locate a font file with matchhing `font_family`, `bold`, and
+ `italic` installed on the current system (usually succeeds if the font is installed).
"""
# ---no-op when empty as fit behavior not defined for that case---
if self.text == "":
return # pragma: no cover
- font_size = self._best_fit_font_size(
- font_family, max_size, bold, italic, font_file
- )
+ font_size = self._best_fit_font_size(font_family, max_size, bold, italic, font_file)
self._apply_fit(font_family, font_size, bold, italic)
@property
- def margin_bottom(self):
- """
- |Length| value representing the inset of text from the bottom text
- frame border. :meth:`pptx.util.Inches` provides a convenient way of
- setting the value, e.g. ``text_frame.margin_bottom = Inches(0.05)``.
+ def margin_bottom(self) -> Length:
+ """|Length| value representing the inset of text from the bottom text frame border.
+
+ :meth:`pptx.util.Inches` provides a convenient way of setting the value, e.g.
+ `text_frame.margin_bottom = Inches(0.05)`.
"""
return self._bodyPr.bIns
@margin_bottom.setter
- def margin_bottom(self, emu):
+ def margin_bottom(self, emu: Length):
self._bodyPr.bIns = emu
@property
- def margin_left(self):
- """
- Inset of text from left text frame border as |Length| value.
- """
+ def margin_left(self) -> Length:
+ """Inset of text from left text frame border as |Length| value."""
return self._bodyPr.lIns
@margin_left.setter
- def margin_left(self, emu):
+ def margin_left(self, emu: Length):
self._bodyPr.lIns = emu
@property
- def margin_right(self):
- """
- Inset of text from right text frame border as |Length| value.
- """
+ def margin_right(self) -> Length:
+ """Inset of text from right text frame border as |Length| value."""
return self._bodyPr.rIns
@margin_right.setter
- def margin_right(self, emu):
+ def margin_right(self, emu: Length):
self._bodyPr.rIns = emu
@property
- def margin_top(self):
- """
- Inset of text from top text frame border as |Length| value.
- """
+ def margin_top(self) -> Length:
+ """Inset of text from top text frame border as |Length| value."""
return self._bodyPr.tIns
@margin_top.setter
- def margin_top(self, emu):
+ def margin_top(self, emu: Length):
self._bodyPr.tIns = emu
@property
- def paragraphs(self):
- """
- Immutable sequence of |_Paragraph| instances corresponding to the
- paragraphs in this text frame. A text frame always contains at least
- one paragraph.
+ def paragraphs(self) -> tuple[_Paragraph, ...]:
+ """Sequence of paragraphs in this text frame.
+
+ A text frame always contains at least one paragraph.
"""
return tuple([_Paragraph(p, self) for p in self._txBody.p_lst])
@property
- def text(self):
- """Unicode/str containing all text in this text-frame.
+ def text(self) -> str:
+ """All text in this text-frame as a single string.
- Read/write. The return value is a str (unicode) containing all text in this
- text-frame. A line-feed character (``"\\n"``) separates the text for each
- paragraph. A vertical-tab character (``"\\v"``) appears for each line break
- (aka. soft carriage-return) encountered.
+ Read/write. The return value contains all text in this text-frame. A line-feed character
+ (`"\\n"`) separates the text for each paragraph. A vertical-tab character (`"\\v"`) appears
+ for each line break (aka. soft carriage-return) encountered.
- The vertical-tab character is how PowerPoint represents a soft carriage return
- in clipboard text, which is why that encoding was chosen.
+ The vertical-tab character is how PowerPoint represents a soft carriage return in clipboard
+ text, which is why that encoding was chosen.
- Assignment replaces all text in the text frame. The assigned value can be
- a 7-bit ASCII string, a UTF-8 encoded 8-bit string, or unicode. A bytes value
- (such as a Python 2 ``str``) is converted to unicode assuming UTF-8 encoding.
- A new paragraph is added for each line-feed character (``"\\n"``) encountered.
- A line-break (soft carriage-return) is inserted for each vertical-tab character
- (``"\\v"``) encountered.
+ Assignment replaces all text in the text frame. A new paragraph is added for each line-feed
+ character (`"\\n"`) encountered. A line-break (soft carriage-return) is inserted for each
+ vertical-tab character (`"\\v"`) encountered.
- Any control character other than newline, tab, or vertical-tab are escaped as
- plain-text like "_x001B_" (for ESC (ASCII 32) in this example).
+ Any control character other than newline, tab, or vertical-tab are escaped as plain-text
+ like "_x001B_" (for ESC (ASCII 32) in this example).
"""
return "\n".join(paragraph.text for paragraph in self.paragraphs)
@text.setter
- def text(self, text):
+ def text(self, text: str):
txBody = self._txBody
txBody.clear_content()
- for p_text in to_unicode(text).split("\n"):
+ for p_text in text.split("\n"):
p = txBody.add_p()
p.append_text(p_text)
@property
- def vertical_anchor(self):
- """
- Read/write member of :ref:`MsoVerticalAnchor` enumeration or |None|,
- representing the vertical alignment of text in this text frame.
- |None| indicates the effective value should be inherited from this
- object's style hierarchy.
+ def vertical_anchor(self) -> MSO_VERTICAL_ANCHOR | None:
+ """Represents the vertical alignment of text in this text frame.
+
+ |None| indicates the effective value should be inherited from this object's style hierarchy.
"""
return self._txBody.bodyPr.anchor
@vertical_anchor.setter
- def vertical_anchor(self, value):
+ def vertical_anchor(self, value: MSO_VERTICAL_ANCHOR | None):
bodyPr = self._txBody.bodyPr
bodyPr.anchor = value
@property
- def word_wrap(self):
- """
- Read-write setting determining whether lines of text in this shape
- are wrapped to fit within the shape's width. Valid values are True,
- False, or None. True and False turn word wrap on and off,
- respectively. Assigning None to word wrap causes any word wrap
- setting to be removed from the text frame, causing it to inherit this
- setting from its style hierarchy.
+ def word_wrap(self) -> bool | None:
+ """`True` when lines of text in this shape are wrapped to fit within the shape's width.
+
+ Read-write. Valid values are True, False, or None. True and False turn word wrap on and
+ off, respectively. Assigning None to word wrap causes any word wrap setting to be removed
+ from the text frame, causing it to inherit this setting from its style hierarchy.
"""
return {
ST_TextWrappingType.SQUARE: True,
@@ -205,7 +205,7 @@ def word_wrap(self):
}[self._txBody.bodyPr.wrap]
@word_wrap.setter
- def word_wrap(self, value):
+ def word_wrap(self, value: bool | None):
if value not in (True, False, None):
raise ValueError( # pragma: no cover
"assigned value must be True, False, or None, got %s" % value
@@ -216,7 +216,7 @@ def word_wrap(self, value):
None: None,
}[value]
- def _apply_fit(self, font_family, font_size, is_bold, is_italic):
+ def _apply_fit(self, font_family: str, font_size: int, is_bold: bool, is_italic: bool):
"""Arrange text in this text frame to fit inside its extents.
This is accomplished by setting auto size off, wrap on, and setting the font of
@@ -226,49 +226,49 @@ def _apply_fit(self, font_family, font_size, is_bold, is_italic):
self.word_wrap = True
self._set_font(font_family, font_size, is_bold, is_italic)
- def _best_fit_font_size(self, family, max_size, bold, italic, font_file):
- """
- Return the largest integer point size not greater than *max_size*
- that allows all the text in this text frame to fit inside its extents
- when rendered using the font described by *family*, *bold*, and
- *italic*. If *font_file* is specified, it is used to calculate the
- fit, whether or not it matches *family*, *bold*, and *italic*.
+ def _best_fit_font_size(
+ self, family: str, max_size: int, bold: bool, italic: bool, font_file: str | None
+ ) -> int:
+ """Return font-size in points that best fits text in this text-frame.
+
+ The best-fit font size is the largest integer point size not greater than `max_size` that
+ allows all the text in this text frame to fit inside its extents when rendered using the
+ font described by `family`, `bold`, and `italic`. If `font_file` is specified, it is used
+ to calculate the fit, whether or not it matches `family`, `bold`, and `italic`.
"""
if font_file is None:
font_file = FontFiles.find(family, bold, italic)
- return TextFitter.best_fit_font_size(
- self.text, self._extents, max_size, font_file
- )
+ return TextFitter.best_fit_font_size(self.text, self._extents, max_size, font_file)
@property
def _bodyPr(self):
return self._txBody.bodyPr
@property
- def _extents(self):
- """
- A (cx, cy) 2-tuple representing the effective rendering area for text
- within this text frame when margins are taken into account.
+ def _extents(self) -> tuple[Length, Length]:
+ """(cx, cy) 2-tuple representing the effective rendering area of this text-frame.
+
+ Margins are taken into account.
"""
+ parent = cast("ProvidesExtents", self._parent)
return (
- self._parent.width - self.margin_left - self.margin_right,
- self._parent.height - self.margin_top - self.margin_bottom,
+ Length(parent.width - self.margin_left - self.margin_right),
+ Length(parent.height - self.margin_top - self.margin_bottom),
)
- def _set_font(self, family, size, bold, italic):
- """
- Set the font properties of all the text in this text frame to
- *family*, *size*, *bold*, and *italic*.
- """
+ def _set_font(self, family: str, size: int, bold: bool, italic: bool):
+ """Set the font properties of all the text in this text frame."""
- def iter_rPrs(txBody):
+ def iter_rPrs(txBody: CT_TextBody) -> Iterator[CT_TextCharacterProperties]:
for p in txBody.p_lst:
for elm in p.content_children:
yield elm.get_or_add_rPr()
# generate a:endParaRPr for each element
yield p.get_or_add_endParaRPr()
- def set_rPr_font(rPr, name, size, bold, italic):
+ def set_rPr_font(
+ rPr: CT_TextCharacterProperties, name: str, size: int, bold: bool, italic: bool
+ ):
f = Font(rPr)
f.name, f.size, f.bold, f.italic = family, Pt(size), bold, italic
@@ -278,70 +278,63 @@ def set_rPr_font(rPr, name, size, bold, italic):
class Font(object):
- """
- Character properties object, providing font size, font name, bold,
- italic, etc. Corresponds to ```` child element of a run. Also
- appears as ```` and ```` in paragraph and
- ```` in list style elements.
+ """Character properties object, providing font size, font name, bold, italic, etc.
+
+ Corresponds to `a:rPr` child element of a run. Also appears as `a:defRPr` and
+ `a:endParaRPr` in paragraph and `a:defRPr` in list style elements.
"""
- def __init__(self, rPr):
+ def __init__(self, rPr: CT_TextCharacterProperties):
super(Font, self).__init__()
self._element = self._rPr = rPr
@property
- def bold(self):
- """
- Get or set boolean bold value of |Font|, e.g.
- ``paragraph.font.bold = True``. If set to |None|, the bold setting is
- cleared and is inherited from an enclosing shape's setting, or a
- setting in a style or master. Returns None if no bold attribute is
- present, meaning the effective bold value is inherited from a master
- or the theme.
+ def bold(self) -> bool | None:
+ """Get or set boolean bold value of |Font|, e.g. `paragraph.font.bold = True`.
+
+ If set to |None|, the bold setting is cleared and is inherited from an enclosing shape's
+ setting, or a setting in a style or master. Returns None if no bold attribute is present,
+ meaning the effective bold value is inherited from a master or the theme.
"""
return self._rPr.b
@bold.setter
- def bold(self, value):
+ def bold(self, value: bool | None):
self._rPr.b = value
@lazyproperty
- def color(self):
- """
- The |ColorFormat| instance that provides access to the color settings
- for this font.
- """
+ def color(self) -> ColorFormat:
+ """The |ColorFormat| instance that provides access to the color settings for this font."""
if self.fill.type != MSO_FILL.SOLID:
self.fill.solid()
return self.fill.fore_color
@lazyproperty
- def fill(self):
- """
- |FillFormat| instance for this font, providing access to fill
- properties such as fill color.
+ def fill(self) -> FillFormat:
+ """|FillFormat| instance for this font.
+
+ Provides access to fill properties such as fill color.
"""
return FillFormat.from_fill_parent(self._rPr)
@property
- def italic(self):
- """
- Get or set boolean italic value of |Font| instance, with the same
- behaviors as bold with respect to None values.
+ def italic(self) -> bool | None:
+ """Get or set boolean italic value of |Font| instance.
+
+ Has the same behaviors as bold with respect to None values.
"""
return self._rPr.i
@italic.setter
- def italic(self, value):
+ def italic(self, value: bool | None):
self._rPr.i = value
@property
- def language_id(self):
- """
- Get or set the language id of this |Font| instance. The language id
- is a member of the :ref:`MsoLanguageId` enumeration. Assigning |None|
- removes any language setting, the same behavior as assigning
- `MSO_LANGUAGE_ID.NONE`.
+ def language_id(self) -> MSO_LANGUAGE_ID | None:
+ """Get or set the language id of this |Font| instance.
+
+ The language id is a member of the :ref:`MsoLanguageId` enumeration. Assigning |None|
+ removes any language setting, the same behavior as assigning `MSO_LANGUAGE_ID.NONE`.
"""
lang = self._rPr.lang
if lang is None:
@@ -349,19 +342,18 @@ def language_id(self):
return self._rPr.lang
@language_id.setter
- def language_id(self, value):
+ def language_id(self, value: MSO_LANGUAGE_ID | None):
if value == MSO_LANGUAGE_ID.NONE:
value = None
self._rPr.lang = value
@property
- def name(self):
- """
- Get or set the typeface name for this |Font| instance, causing the
- text it controls to appear in the named font, if a matching font is
- found. Returns |None| if the typeface is currently inherited from the
- theme. Setting it to |None| removes any override of the theme
- typeface.
+ def name(self) -> str | None:
+ """Get or set the typeface name for this |Font| instance.
+
+ Causes the text it controls to appear in the named font, if a matching font is found.
+ Returns |None| if the typeface is currently inherited from the theme. Setting it to |None|
+ removes any override of the theme typeface.
"""
latin = self._rPr.latin
if latin is None:
@@ -369,28 +361,26 @@ def name(self):
return latin.typeface
@name.setter
- def name(self, value):
+ def name(self, value: str | None):
if value is None:
- self._rPr._remove_latin()
+ self._rPr._remove_latin() # pyright: ignore[reportPrivateUsage]
else:
latin = self._rPr.get_or_add_latin()
latin.typeface = value
@property
- def size(self):
- """
- Read/write |Length| value or |None|, indicating the font height in
- English Metric Units (EMU). |None| indicates the font size should be
- inherited from its style hierarchy, such as a placeholder or document
- defaults (usually 18pt). |Length| is a subclass of |int| having
- properties for convenient conversion into points or other length
- units. Likewise, the :class:`pptx.util.Pt` class allows convenient
- specification of point values::
-
- >> font.size = Pt(24)
- >> font.size
+ def size(self) -> Length | None:
+ """Indicates the font height in English Metric Units (EMU).
+
+ Read/write. |None| indicates the font size should be inherited from its style hierarchy,
+ such as a placeholder or document defaults (usually 18pt). |Length| is a subclass of |int|
+ having properties for convenient conversion into points or other length units. Likewise,
+ the :class:`pptx.util.Pt` class allows convenient specification of point values::
+
+ >>> font.size = Pt(24)
+ >>> font.size
304800
- >> font.size.pt
+ >>> font.size.pt
24.0
"""
sz = self._rPr.sz
@@ -399,7 +389,7 @@ def size(self):
return Centipoints(sz)
@size.setter
- def size(self, emu):
+ def size(self, emu: Length | None):
if emu is None:
self._rPr.sz = None
else:
@@ -407,16 +397,14 @@ def size(self, emu):
self._rPr.sz = sz
@property
- def underline(self):
- """
- Read/write. |True|, |False|, |None|, or a member of the
- :ref:`MsoTextUnderlineType` enumeration indicating the underline
- setting for this font. |None| is the default and indicates the
- underline setting should be inherited from the style hierarchy, such
- as from a placeholder. |True| indicates single underline. |False|
- indicates no underline. Other settings such as double and wavy
- underlining are indicated with members of the
- :ref:`MsoTextUnderlineType` enumeration.
+ def underline(self) -> bool | MSO_TEXT_UNDERLINE_TYPE | None:
+ """Indicaties the underline setting for this font.
+
+ Value is |True|, |False|, |None|, or a member of the :ref:`MsoTextUnderlineType`
+ enumeration. |None| is the default and indicates the underline setting should be inherited
+ from the style hierarchy, such as from a placeholder. |True| indicates single underline.
+ |False| indicates no underline. Other settings such as double and wavy underlining are
+ indicated with members of the :ref:`MsoTextUnderlineType` enumeration.
"""
u = self._rPr.u
if u is MSO_UNDERLINE.NONE:
@@ -426,7 +414,7 @@ def underline(self):
return u
@underline.setter
- def underline(self, value):
+ def underline(self, value: bool | MSO_TEXT_UNDERLINE_TYPE | None):
if value is True:
value = MSO_UNDERLINE.SINGLE_LINE
elif value is False:
@@ -435,51 +423,51 @@ def underline(self, value):
class _Hyperlink(Subshape):
- """
- Text run hyperlink object. Corresponds to ```` child
- element of the run's properties element (````).
+ """Text run hyperlink object.
+
+ Corresponds to `a:hlinkClick` child element of the run's properties element (`a:rPr`).
"""
- def __init__(self, rPr, parent):
+ def __init__(self, rPr: CT_TextCharacterProperties, parent: ProvidesPart):
super(_Hyperlink, self).__init__(parent)
self._rPr = rPr
@property
- def address(self):
- """
- Read/write. The URL of the hyperlink. URL can be on http, https,
- mailto, or file scheme; others may work.
+ def address(self) -> str | None:
+ """The URL of the hyperlink.
+
+ Read/write. URL can be on http, https, mailto, or file scheme; others may work.
"""
if self._hlinkClick is None:
return None
return self.part.target_ref(self._hlinkClick.rId)
@address.setter
- def address(self, url):
+ def address(self, url: str | None):
# implements all three of add, change, and remove hyperlink
if self._hlinkClick is not None:
self._remove_hlinkClick()
if url:
self._add_hlinkClick(url)
- def _add_hlinkClick(self, url):
+ def _add_hlinkClick(self, url: str):
rId = self.part.relate_to(url, RT.HYPERLINK, is_external=True)
self._rPr.add_hlinkClick(rId)
@property
- def _hlinkClick(self):
+ def _hlinkClick(self) -> CT_Hyperlink | None:
return self._rPr.hlinkClick
def _remove_hlinkClick(self):
assert self._hlinkClick is not None
self.part.drop_rel(self._hlinkClick.rId)
- self._rPr._remove_hlinkClick()
+ self._rPr._remove_hlinkClick() # pyright: ignore[reportPrivateUsage]
class _Paragraph(Subshape):
"""Paragraph object. Not intended to be constructed directly."""
- def __init__(self, p, parent):
+ def __init__(self, p: CT_TextParagraph, parent: ProvidesPart):
super(_Paragraph, self).__init__(parent)
self._element = self._p = p
@@ -487,73 +475,67 @@ def add_line_break(self):
"""Add line break at end of this paragraph."""
self._p.add_br()
- def add_run(self):
- """
- Return a new run appended to the runs in this paragraph.
- """
+ def add_run(self) -> _Run:
+ """Return a new run appended to the runs in this paragraph."""
r = self._p.add_r()
return _Run(r, self)
@property
- def alignment(self):
- """
- Horizontal alignment of this paragraph, represented by either
- a member of the enumeration :ref:`PpParagraphAlignment` or |None|.
- The value |None| indicates the paragraph should 'inherit' its
- effective value from its style hierarchy. Assigning |None| removes
- any explicit setting, causing its inherited value to be used.
+ def alignment(self) -> PP_PARAGRAPH_ALIGNMENT | None:
+ """Horizontal alignment of this paragraph.
+
+ The value |None| indicates the paragraph should 'inherit' its effective value from its
+ style hierarchy. Assigning |None| removes any explicit setting, causing its inherited
+ value to be used.
"""
return self._pPr.algn
@alignment.setter
- def alignment(self, value):
+ def alignment(self, value: PP_PARAGRAPH_ALIGNMENT | None):
self._pPr.algn = value
def clear(self):
- """
- Remove all content from this paragraph. Paragraph properties are
- preserved. Content includes runs, line breaks, and fields.
+ """Remove all content from this paragraph.
+
+ Paragraph properties are preserved. Content includes runs, line breaks, and fields.
"""
for elm in self._element.content_children:
self._element.remove(elm)
return self
@property
- def font(self):
- """
- |Font| object containing default character properties for the runs in
- this paragraph. These character properties override default properties
- inherited from parent objects such as the text frame the paragraph is
- contained in and they may be overridden by character properties set at
- the run level.
+ def font(self) -> Font:
+ """|Font| object containing default character properties for the runs in this paragraph.
+
+ These character properties override default properties inherited from parent objects such
+ as the text frame the paragraph is contained in and they may be overridden by character
+ properties set at the run level.
"""
return Font(self._defRPr)
@property
- def level(self):
- """
- Read-write integer indentation level of this paragraph, having a
- range of 0-8 inclusive. 0 represents a top-level paragraph and is the
- default value. Indentation level is most commonly encountered in a
- bulleted list, as is found on a word bullet slide.
+ def level(self) -> int:
+ """Indentation level of this paragraph.
+
+ Read-write. Integer in range 0..8 inclusive. 0 represents a top-level paragraph and is the
+ default value. Indentation level is most commonly encountered in a bulleted list, as is
+ found on a word bullet slide.
"""
return self._pPr.lvl
@level.setter
- def level(self, level):
+ def level(self, level: int):
self._pPr.lvl = level
@property
- def line_spacing(self):
- """
- Numeric or |Length| value specifying the space between baselines in
- successive lines of this paragraph. A value of |None| indicates no
- explicit value is assigned and its effective value is inherited from
- the paragraph's style hierarchy. A numeric value, e.g. `2` or `1.5`,
- indicates spacing is applied in multiples of line heights. A |Length|
- value such as ``Pt(12)`` indicates spacing is a fixed height. The
- |Pt| value class is a convenient way to apply line spacing in units
- of points.
+ def line_spacing(self) -> int | float | Length | None:
+ """The space between baselines in successive lines of this paragraph.
+
+ A value of |None| indicates no explicit value is assigned and its effective value is
+ inherited from the paragraph's style hierarchy. A numeric value, e.g. `2` or `1.5`,
+ indicates spacing is applied in multiples of line heights. A |Length| value such as
+ `Pt(12)` indicates spacing is a fixed height. The |Pt| value class is a convenient way to
+ apply line spacing in units of points.
"""
pPr = self._p.pPr
if pPr is None:
@@ -561,27 +543,23 @@ def line_spacing(self):
return pPr.line_spacing
@line_spacing.setter
- def line_spacing(self, value):
+ def line_spacing(self, value: int | float | Length | None):
pPr = self._p.get_or_add_pPr()
pPr.line_spacing = value
@property
- def runs(self):
- """
- Immutable sequence of |_Run| objects corresponding to the runs in
- this paragraph.
- """
+ def runs(self) -> tuple[_Run, ...]:
+ """Sequence of runs in this paragraph."""
return tuple(_Run(r, self) for r in self._element.r_lst)
@property
- def space_after(self):
- """
- |Length| value specifying the spacing to appear between this
- paragraph and the subsequent paragraph. A value of |None| indicates
- no explicit value is assigned and its effective value is inherited
- from the paragraph's style hierarchy. |Length| objects provide
- convenience properties, such as ``.pt`` and ``.inches``, that allow
- easy conversion to various length units.
+ def space_after(self) -> Length | None:
+ """The spacing to appear between this paragraph and the subsequent paragraph.
+
+ A value of |None| indicates no explicit value is assigned and its effective value is
+ inherited from the paragraph's style hierarchy. |Length| objects provide convenience
+ properties, such as `.pt` and `.inches`, that allow easy conversion to various length
+ units.
"""
pPr = self._p.pPr
if pPr is None:
@@ -589,19 +567,17 @@ def space_after(self):
return pPr.space_after
@space_after.setter
- def space_after(self, value):
+ def space_after(self, value: Length | None):
pPr = self._p.get_or_add_pPr()
pPr.space_after = value
@property
- def space_before(self):
- """
- |Length| value specifying the spacing to appear between this
- paragraph and the prior paragraph. A value of |None| indicates no
- explicit value is assigned and its effective value is inherited from
- the paragraph's style hierarchy. |Length| objects provide convenience
- properties, such as ``.pt`` and ``.cm``, that allow easy conversion
- to various length units.
+ def space_before(self) -> Length | None:
+ """The spacing to appear between this paragraph and the prior paragraph.
+
+ A value of |None| indicates no explicit value is assigned and its effective value is
+ inherited from the paragraph's style hierarchy. |Length| objects provide convenience
+ properties, such as `.pt` and `.cm`, that allow easy conversion to various length units.
"""
pPr = self._p.pPr
if pPr is None:
@@ -609,88 +585,78 @@ def space_before(self):
return pPr.space_before
@space_before.setter
- def space_before(self, value):
+ def space_before(self, value: Length | None):
pPr = self._p.get_or_add_pPr()
pPr.space_before = value
@property
- def text(self):
- """str (unicode) representation of paragraph contents.
-
- Read/write. This value is formed by concatenating the text in each run and field
- making up the paragraph, adding a vertical-tab character (``"\\v"``) for each
- line-break element (``, soft carriage-return) encountered.
+ def text(self) -> str:
+ """Text of paragraph as a single string.
- While the encoding of line-breaks as a vertical tab might be surprising at
- first, doing so is consistent with PowerPoint's clipboard copy behavior and
- allows a line-break to be distinguished from a paragraph boundary within the str
- return value.
+ Read/write. This value is formed by concatenating the text in each run and field making up
+ the paragraph, adding a vertical-tab character (`"\\v"`) for each line-break element
+ (``, soft carriage-return) encountered.
- Assignment causes all content in the paragraph to be replaced. Each vertical-tab
- character (``"\\v"``) in the assigned str is translated to a line-break, as is
- each line-feed character (``"\\n"``). Contrast behavior of line-feed character
- in `TextFrame.text` setter. If line-feed characters are intended to produce new
- paragraphs, use `TextFrame.text` instead. Any other control characters in the
- assigned string are escaped as a hex representation like "_x001B_" (for ESC
- (ASCII 27) in this example).
+ While the encoding of line-breaks as a vertical tab might be surprising at first, doing so
+ is consistent with PowerPoint's clipboard copy behavior and allows a line-break to be
+ distinguished from a paragraph boundary within the str return value.
- The assigned value can be a 7-bit ASCII byte string (Python 2 str), a UTF-8
- encoded 8-bit byte string (Python 2 str), or unicode. Bytes values are converted
- to unicode assuming UTF-8 encoding.
+ Assignment causes all content in the paragraph to be replaced. Each vertical-tab character
+ (`"\\v"`) in the assigned str is translated to a line-break, as is each line-feed
+ character (`"\\n"`). Contrast behavior of line-feed character in `TextFrame.text` setter.
+ If line-feed characters are intended to produce new paragraphs, use `TextFrame.text`
+ instead. Any other control characters in the assigned string are escaped as a hex
+ representation like "_x001B_" (for ESC (ASCII 27) in this example).
"""
return "".join(elm.text for elm in self._element.content_children)
@text.setter
- def text(self, text):
+ def text(self, text: str):
self.clear()
- self._element.append_text(to_unicode(text))
+ self._element.append_text(text)
@property
- def _defRPr(self):
- """
- The |CT_TextCharacterProperties| instance ( element) that
- defines the default run properties for runs in this paragraph. Causes
- the element to be added if not present.
+ def _defRPr(self) -> CT_TextCharacterProperties:
+ """The element that defines the default run properties for runs in this paragraph.
+
+ Causes the element to be added if not present.
"""
return self._pPr.get_or_add_defRPr()
@property
- def _pPr(self):
- """
- The |CT_TextParagraphProperties| instance for this paragraph, the
- element containing its paragraph properties. Causes the
- element to be added if not present.
+ def _pPr(self) -> CT_TextParagraphProperties:
+ """Contains the properties for this paragraph.
+
+ Causes the element to be added if not present.
"""
return self._p.get_or_add_pPr()
class _Run(Subshape):
- """Text run object. Corresponds to ```` child element in a paragraph."""
+ """Text run object. Corresponds to `a:r` child element in a paragraph."""
- def __init__(self, r, parent):
+ def __init__(self, r: CT_RegularTextRun, parent: ProvidesPart):
super(_Run, self).__init__(parent)
self._r = r
@property
def font(self):
- """
- |Font| instance containing run-level character properties for the
- text in this run. Character properties can be and perhaps most often
- are inherited from parent objects such as the paragraph and slide
- layout the run is contained in. Only those specifically overridden at
- the run level are contained in the font object.
+ """|Font| instance containing run-level character properties for the text in this run.
+
+ Character properties can be and perhaps most often are inherited from parent objects such
+ as the paragraph and slide layout the run is contained in. Only those specifically
+ overridden at the run level are contained in the font object.
"""
rPr = self._r.get_or_add_rPr()
return Font(rPr)
@lazyproperty
- def hyperlink(self):
- """
- |_Hyperlink| instance acting as proxy for any ````
- element under the run properties element. Created on demand, the
- hyperlink object is available whether an ```` element
- is present or not, and creates or deletes that element as appropriate
- in response to actions on its methods and attributes.
+ def hyperlink(self) -> _Hyperlink:
+ """Proxy for any `a:hlinkClick` element under the run properties element.
+
+ Created on demand, the hyperlink object is available whether an `a:hlinkClick` element is
+ present or not, and creates or deletes that element as appropriate in response to actions
+ on its methods and attributes.
"""
rPr = self._r.get_or_add_rPr()
return _Hyperlink(rPr, self)
@@ -711,5 +677,5 @@ def text(self):
return self._r.text
@text.setter
- def text(self, str):
- self._r.text = to_unicode(str)
+ def text(self, text: str):
+ self._r.text = text
diff --git a/src/pptx/types.py b/src/pptx/types.py
new file mode 100644
index 000000000..46d86661b
--- /dev/null
+++ b/src/pptx/types.py
@@ -0,0 +1,36 @@
+"""Abstract types used by `python-pptx`."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from typing_extensions import Protocol
+
+if TYPE_CHECKING:
+ from pptx.opc.package import XmlPart
+ from pptx.util import Length
+
+
+class ProvidesExtents(Protocol):
+ """An object that has width and height."""
+
+ @property
+ def height(self) -> Length:
+ """Distance between top and bottom extents of shape in EMUs."""
+ ...
+
+ @property
+ def width(self) -> Length:
+ """Distance between left and right extents of shape in EMUs."""
+ ...
+
+
+class ProvidesPart(Protocol):
+ """An object that provides access to its XmlPart.
+
+ This type is for objects that need access to their part, possibly because they need access to
+ the package or related parts.
+ """
+
+ @property
+ def part(self) -> XmlPart: ...
diff --git a/src/pptx/util.py b/src/pptx/util.py
index 5e5d92ecd..bbe8ac204 100644
--- a/src/pptx/util.py
+++ b/src/pptx/util.py
@@ -1,16 +1,15 @@
-# encoding: utf-8
-
"""Utility functions and classes."""
-from __future__ import division
+from __future__ import annotations
import functools
+from typing import Any, Callable, Generic, TypeVar, cast
class Length(int):
- """
- Base class for length classes Inches, Emu, Cm, Mm, Pt, and Px. Provides
- properties for converting length values to convenient units.
+ """Base class for length classes Inches, Emu, Cm, Mm, Pt, and Px.
+
+ Provides properties for converting length values to convenient units.
"""
_EMUS_PER_INCH = 914400
@@ -19,149 +18,124 @@ class Length(int):
_EMUS_PER_MM = 36000
_EMUS_PER_PT = 12700
- def __new__(cls, emu):
+ def __new__(cls, emu: int):
return int.__new__(cls, emu)
@property
- def inches(self):
- """
- Floating point length in inches
- """
+ def inches(self) -> float:
+ """Floating point length in inches."""
return self / float(self._EMUS_PER_INCH)
@property
- def centipoints(self):
- """
- Integer length in hundredths of a point (1/7200 inch). Used
- internally because PowerPoint stores font size in centipoints.
+ def centipoints(self) -> int:
+ """Integer length in hundredths of a point (1/7200 inch).
+
+ Used internally because PowerPoint stores font size in centipoints.
"""
return self // self._EMUS_PER_CENTIPOINT
@property
- def cm(self):
- """
- Floating point length in centimeters
- """
+ def cm(self) -> float:
+ """Floating point length in centimeters."""
return self / float(self._EMUS_PER_CM)
@property
- def emu(self):
- """
- Integer length in English Metric Units
- """
+ def emu(self) -> int:
+ """Integer length in English Metric Units."""
return self
@property
- def mm(self):
- """
- Floating point length in millimeters
- """
+ def mm(self) -> float:
+ """Floating point length in millimeters."""
return self / float(self._EMUS_PER_MM)
@property
- def pt(self):
- """
- Floating point length in points
- """
+ def pt(self) -> float:
+ """Floating point length in points."""
return self / float(self._EMUS_PER_PT)
class Inches(Length):
- """
- Convenience constructor for length in inches
- """
+ """Convenience constructor for length in inches."""
- def __new__(cls, inches):
+ def __new__(cls, inches: float):
emu = int(inches * Length._EMUS_PER_INCH)
return Length.__new__(cls, emu)
class Centipoints(Length):
- """
- Convenience constructor for length in hundredths of a point
- """
+ """Convenience constructor for length in hundredths of a point."""
- def __new__(cls, centipoints):
+ def __new__(cls, centipoints: int):
emu = int(centipoints * Length._EMUS_PER_CENTIPOINT)
return Length.__new__(cls, emu)
class Cm(Length):
- """
- Convenience constructor for length in centimeters
- """
+ """Convenience constructor for length in centimeters."""
- def __new__(cls, cm):
+ def __new__(cls, cm: float):
emu = int(cm * Length._EMUS_PER_CM)
return Length.__new__(cls, emu)
class Emu(Length):
- """
- Convenience constructor for length in english metric units
- """
+ """Convenience constructor for length in english metric units."""
- def __new__(cls, emu):
+ def __new__(cls, emu: int):
return Length.__new__(cls, int(emu))
class Mm(Length):
- """
- Convenience constructor for length in millimeters
- """
+ """Convenience constructor for length in millimeters."""
- def __new__(cls, mm):
+ def __new__(cls, mm: float):
emu = int(mm * Length._EMUS_PER_MM)
return Length.__new__(cls, emu)
class Pt(Length):
- """
- Convenience value class for specifying a length in points
- """
+ """Convenience value class for specifying a length in points."""
- def __new__(cls, points):
+ def __new__(cls, points: float):
emu = int(points * Length._EMUS_PER_PT)
return Length.__new__(cls, emu)
-class lazyproperty(object):
+_T = TypeVar("_T")
+
+
+class lazyproperty(Generic[_T]):
"""Decorator like @property, but evaluated only on first access.
- Like @property, this can only be used to decorate methods having only
- a `self` parameter, and is accessed like an attribute on an instance,
- i.e. trailing parentheses are not used. Unlike @property, the decorated
- method is only evaluated on first access; the resulting value is cached
- and that same value returned on second and later access without
- re-evaluation of the method.
-
- Like @property, this class produces a *data descriptor* object, which is
- stored in the __dict__ of the *class* under the name of the decorated
- method ('fget' nominally). The cached value is stored in the __dict__ of
- the *instance* under that same name.
-
- Because it is a data descriptor (as opposed to a *non-data descriptor*),
- its `__get__()` method is executed on each access of the decorated
- attribute; the __dict__ item of the same name is "shadowed" by the
- descriptor.
-
- While this may represent a performance improvement over a property, its
- greater benefit may be its other characteristics. One common use is to
- construct collaborator objects, removing that "real work" from the
- constructor, while still only executing once. It also de-couples client
- code from any sequencing considerations; if it's accessed from more than
- one location, it's assured it will be ready whenever needed.
+ Like @property, this can only be used to decorate methods having only a `self` parameter, and
+ is accessed like an attribute on an instance, i.e. trailing parentheses are not used. Unlike
+ @property, the decorated method is only evaluated on first access; the resulting value is
+ cached and that same value returned on second and later access without re-evaluation of the
+ method.
+
+ Like @property, this class produces a *data descriptor* object, which is stored in the __dict__
+ of the *class* under the name of the decorated method ('fget' nominally). The cached value is
+ stored in the __dict__ of the *instance* under that same name.
+
+ Because it is a data descriptor (as opposed to a *non-data descriptor*), its `__get__()` method
+ is executed on each access of the decorated attribute; the __dict__ item of the same name is
+ "shadowed" by the descriptor.
+
+ While this may represent a performance improvement over a property, its greater benefit may be
+ its other characteristics. One common use is to construct collaborator objects, removing that
+ "real work" from the constructor, while still only executing once. It also de-couples client
+ code from any sequencing considerations; if it's accessed from more than one location, it's
+ assured it will be ready whenever needed.
Loosely based on: https://stackoverflow.com/a/6849299/1902513.
- A lazyproperty is read-only. There is no counterpart to the optional
- "setter" (or deleter) behavior of an @property. This is critically
- important to maintaining its immutability and idempotence guarantees.
- Attempting to assign to a lazyproperty raises AttributeError
+ A lazyproperty is read-only. There is no counterpart to the optional "setter" (or deleter)
+ behavior of an @property. This is critically important to maintaining its immutability and
+ idempotence guarantees. Attempting to assign to a lazyproperty raises AttributeError
unconditionally.
- The parameter names in the methods below correspond to this usage
- example::
+ The parameter names in the methods below correspond to this usage example::
class Obj(object)
@@ -171,68 +145,70 @@ def fget(self):
obj = Obj()
- Not suitable for wrapping a function (as opposed to a method) because it
- is not callable.
+ Not suitable for wrapping a function (as opposed to a method) because it is not callable.
"""
- def __init__(self, fget):
+ def __init__(self, fget: Callable[..., _T]) -> None:
"""*fget* is the decorated method (a "getter" function).
- A lazyproperty is read-only, so there is only an *fget* function (a
- regular @property can also have an fset and fdel function). This name
- was chosen for consistency with Python's `property` class which uses
- this name for the corresponding parameter.
+ A lazyproperty is read-only, so there is only an *fget* function (a regular
+ @property can also have an fset and fdel function). This name was chosen for
+ consistency with Python's `property` class which uses this name for the
+ corresponding parameter.
"""
- # ---maintain a reference to the wrapped getter method
+ # --- maintain a reference to the wrapped getter method
self._fget = fget
- # ---adopt fget's __name__, __doc__, and other attributes
- functools.update_wrapper(self, fget)
+ # --- and store the name of that decorated method
+ self._name = fget.__name__
+ # --- adopt fget's __name__, __doc__, and other attributes
+ functools.update_wrapper(self, fget) # pyright: ignore
- def __get__(self, obj, type=None):
+ def __get__(self, obj: Any, type: Any = None) -> _T:
"""Called on each access of 'fget' attribute on class or instance.
- *self* is this instance of a lazyproperty descriptor "wrapping" the
- property method it decorates (`fget`, nominally).
+ *self* is this instance of a lazyproperty descriptor "wrapping" the property
+ method it decorates (`fget`, nominally).
- *obj* is the "host" object instance when the attribute is accessed
- from an object instance, e.g. `obj = Obj(); obj.fget`. *obj* is None
- when accessed on the class, e.g. `Obj.fget`.
+ *obj* is the "host" object instance when the attribute is accessed from an
+ object instance, e.g. `obj = Obj(); obj.fget`. *obj* is None when accessed on
+ the class, e.g. `Obj.fget`.
- *type* is the class hosting the decorated getter method (`fget`) on
- both class and instance attribute access.
+ *type* is the class hosting the decorated getter method (`fget`) on both class
+ and instance attribute access.
"""
- # ---when accessed on class, e.g. Obj.fget, just return this
- # ---descriptor instance (patched above to look like fget).
+ # --- when accessed on class, e.g. Obj.fget, just return this descriptor
+ # --- instance (patched above to look like fget).
if obj is None:
- return self
+ return self # type: ignore
- # ---when accessed on instance, start by checking instance __dict__
- value = obj.__dict__.get(self.__name__)
+ # --- when accessed on instance, start by checking instance __dict__ for
+ # --- item with key matching the wrapped function's name
+ value = obj.__dict__.get(self._name)
if value is None:
- # ---on first access, __dict__ item will absent. Evaluate fget()
- # ---and store that value in the (otherwise unused) host-object
- # ---__dict__ value of same name ('fget' nominally)
+ # --- on first access, the __dict__ item will be absent. Evaluate fget()
+ # --- and store that value in the (otherwise unused) host-object
+ # --- __dict__ value of same name ('fget' nominally)
value = self._fget(obj)
- obj.__dict__[self.__name__] = value
- return value
+ obj.__dict__[self._name] = value
+ return cast(_T, value)
- def __set__(self, obj, value):
+ def __set__(self, obj: Any, value: Any) -> None:
"""Raises unconditionally, to preserve read-only behavior.
- This decorator is intended to implement immutable (and idempotent)
- object attributes. For that reason, assignment to this property must
- be explicitly prevented.
-
- If this __set__ method was not present, this descriptor would become
- a *non-data descriptor*. That would be nice because the cached value
- would be accessed directly once set (__dict__ attrs have precedence
- over non-data descriptors on instance attribute lookup). The problem
- is, there would be nothing to stop assignment to the cached value,
- which would overwrite the result of `fget()` and break both the
- immutability and idempotence guarantees of this decorator.
-
- The performance with this __set__() method in place was roughly 0.4
- usec per access when measured on a 2.8GHz development machine; so
- quite snappy and probably not a rich target for optimization efforts.
+ This decorator is intended to implement immutable (and idempotent) object
+ attributes. For that reason, assignment to this property must be explicitly
+ prevented.
+
+ If this __set__ method was not present, this descriptor would become a
+ *non-data descriptor*. That would be nice because the cached value would be
+ accessed directly once set (__dict__ attrs have precedence over non-data
+ descriptors on instance attribute lookup). The problem is, there would be
+ nothing to stop assignment to the cached value, which would overwrite the result
+ of `fget()` and break both the immutability and idempotence guarantees of this
+ decorator.
+
+ The performance with this __set__() method in place was roughly 0.4 usec per
+ access when measured on a 2.8GHz development machine; so quite snappy and
+ probably not a rich target for optimization efforts.
"""
- raise AttributeError("can't set attribute") # pragma: no cover
+ raise AttributeError("can't set attribute")
diff --git a/tests/chart/test_axis.py b/tests/chart/test_axis.py
index aa0ce302f..9dbb50f51 100644
--- a/tests/chart/test_axis.py
+++ b/tests/chart/test_axis.py
@@ -1,25 +1,29 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
-"""Unit-test suite for pptx.chart.axis module."""
+"""Unit-test suite for `pptx.chart.axis` module."""
+
+from __future__ import annotations
import pytest
from pptx.chart.axis import (
AxisTitle,
- _BaseAxis,
CategoryAxis,
DateAxis,
MajorGridlines,
TickLabels,
ValueAxis,
+ _BaseAxis,
)
from pptx.dml.chtfmt import ChartFormat
from pptx.enum.chart import (
XL_AXIS_CROSSES,
XL_CATEGORY_TYPE,
- XL_TICK_LABEL_POSITION as XL_TICK_LBL_POS,
XL_TICK_MARK,
)
+from pptx.enum.chart import (
+ XL_TICK_LABEL_POSITION as XL_TICK_LBL_POS,
+)
from pptx.text.text import Font
from ..unitutil.cxml import element, xml
@@ -116,9 +120,7 @@ def it_knows_whether_it_renders_in_reverse_order(self, reverse_order_get_fixture
xAx, expected_value = reverse_order_get_fixture
assert _BaseAxis(xAx).reverse_order == expected_value
- def it_can_change_whether_it_renders_in_reverse_order(
- self, reverse_order_set_fixture
- ):
+ def it_can_change_whether_it_renders_in_reverse_order(self, reverse_order_set_fixture):
xAx, new_value, expected_xml = reverse_order_set_fixture
axis = _BaseAxis(xAx)
@@ -655,9 +657,7 @@ def visible_set_fixture(self, request):
@pytest.fixture
def AxisTitle_(self, request, axis_title_):
- return class_mock(
- request, "pptx.chart.axis.AxisTitle", return_value=axis_title_
- )
+ return class_mock(request, "pptx.chart.axis.AxisTitle", return_value=axis_title_)
@pytest.fixture
def axis_title_(self, request):
@@ -673,9 +673,7 @@ def format_(self, request):
@pytest.fixture
def MajorGridlines_(self, request, major_gridlines_):
- return class_mock(
- request, "pptx.chart.axis.MajorGridlines", return_value=major_gridlines_
- )
+ return class_mock(request, "pptx.chart.axis.MajorGridlines", return_value=major_gridlines_)
@pytest.fixture
def major_gridlines_(self, request):
@@ -683,9 +681,7 @@ def major_gridlines_(self, request):
@pytest.fixture
def TickLabels_(self, request, tick_labels_):
- return class_mock(
- request, "pptx.chart.axis.TickLabels", return_value=tick_labels_
- )
+ return class_mock(request, "pptx.chart.axis.TickLabels", return_value=tick_labels_)
@pytest.fixture
def tick_labels_(self, request):
@@ -740,20 +736,17 @@ def has_tf_get_fixture(self, request):
(
"c:title{a:b=c}",
True,
- "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr"
- ")",
+ "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr" ")",
),
(
"c:title{a:b=c}/c:tx",
True,
- "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr"
- ")",
+ "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr" ")",
),
(
"c:title{a:b=c}/c:tx/c:strRef",
True,
- "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr"
- ")",
+ "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr" ")",
),
("c:title/c:tx/c:rich", True, "c:title/c:tx/c:rich"),
("c:title", False, "c:title"),
@@ -822,9 +815,7 @@ def it_provides_access_to_its_format(self, format_fixture):
gridlines, expected_xml, ChartFormat_, format_ = format_fixture
format = gridlines.format
assert gridlines._xAx.xml == expected_xml
- ChartFormat_.assert_called_once_with(
- gridlines._xAx.xpath("c:majorGridlines")[0]
- )
+ ChartFormat_.assert_called_once_with(gridlines._xAx.xpath("c:majorGridlines")[0])
assert format is format_
# fixtures -------------------------------------------------------
@@ -873,9 +864,7 @@ def it_can_change_its_number_format(self, number_format_set_fixture):
tick_labels.number_format = new_value
assert tick_labels._element.xml == expected_xml
- def it_knows_whether_its_number_format_is_linked(
- self, number_format_is_linked_get_fixture
- ):
+ def it_knows_whether_its_number_format_is_linked(self, number_format_is_linked_get_fixture):
tick_labels, expected_value = number_format_is_linked_get_fixture
assert tick_labels.number_format_is_linked is expected_value
diff --git a/tests/chart/test_category.py b/tests/chart/test_category.py
index 28bbeb096..9319d664b 100644
--- a/tests/chart/test_category.py
+++ b/tests/chart/test_category.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for the `pptx.chart.category` module."""
-"""
-Unit test suite for the pptx.chart.category module.
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
@@ -28,7 +24,12 @@ def it_supports_indexed_access(self, getitem_fixture):
assert category is category_
def it_can_iterate_over_the_categories_it_contains(self, iter_fixture):
- categories, expected_categories, Category_, calls, = iter_fixture
+ (
+ categories,
+ expected_categories,
+ Category_,
+ calls,
+ ) = iter_fixture
assert [c for c in categories] == expected_categories
assert Category_.call_args_list == calls
@@ -117,9 +118,7 @@ def iter_fixture(self, Category_, category_):
calls = [call(None, 0), call(pt, 1)]
return categories, expected_categories, Category_, calls
- @pytest.fixture(
- params=[("c:barChart", 0), ("c:barChart/c:ser/c:cat/c:ptCount{val=4}", 4)]
- )
+ @pytest.fixture(params=[("c:barChart", 0), ("c:barChart/c:ser/c:cat/c:ptCount{val=4}", 4)])
def len_fixture(self, request):
xChart_cxml, expected_len = request.param
categories = Categories(element(xChart_cxml))
@@ -147,9 +146,7 @@ def levels_fixture(self, request, CategoryLevel_, category_level_):
@pytest.fixture
def Category_(self, request, category_):
- return class_mock(
- request, "pptx.chart.category.Category", return_value=category_
- )
+ return class_mock(request, "pptx.chart.category.Category", return_value=category_)
@pytest.fixture
def category_(self, request):
@@ -245,9 +242,7 @@ def len_fixture(self, request):
@pytest.fixture
def Category_(self, request, category_):
- return class_mock(
- request, "pptx.chart.category.Category", return_value=category_
- )
+ return class_mock(request, "pptx.chart.category.Category", return_value=category_)
@pytest.fixture
def category_(self, request):
diff --git a/tests/chart/test_chart.py b/tests/chart/test_chart.py
index 8b89c902a..667253347 100644
--- a/tests/chart/test_chart.py
+++ b/tests/chart/test_chart.py
@@ -1,7 +1,9 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.chart.chart` module."""
+from __future__ import annotations
+
import pytest
from pptx.chart.axis import CategoryAxis, DateAxis, ValueAxis
@@ -36,9 +38,7 @@ def it_provides_access_to_its_font(self, font_fixture, Font_, font_):
font = chart.font
assert chartSpace.xml == expected_xml
- Font_.assert_called_once_with(
- chartSpace.xpath("./c:txPr/a:p/a:pPr/a:defRPr")[0]
- )
+ Font_.assert_called_once_with(chartSpace.xpath("./c:txPr/a:p/a:pPr/a:defRPr")[0])
assert font is font_
def it_knows_whether_it_has_a_title(self, has_title_get_fixture):
@@ -171,8 +171,7 @@ def cat_ax_raise_fixture(self):
params=[
(
"c:chartSpace{a:b=c}",
- "c:chartSpace{a:b=c}/c:txPr/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr"
- ")",
+ "c:chartSpace{a:b=c}/c:txPr/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr" ")",
),
("c:chartSpace/c:txPr/a:p", "c:chartSpace/c:txPr/a:p/a:pPr/a:defRPr"),
(
@@ -198,9 +197,7 @@ def has_legend_get_fixture(self, request):
chart = Chart(element(chartSpace_cxml), None)
return chart, expected_value
- @pytest.fixture(
- params=[("c:chartSpace/c:chart", True, "c:chartSpace/c:chart/c:legend")]
- )
+ @pytest.fixture(params=[("c:chartSpace/c:chart", True, "c:chartSpace/c:chart/c:legend")])
def has_legend_set_fixture(self, request):
chartSpace_cxml, new_value, expected_chartSpace_cxml = request.param
chart = Chart(element(chartSpace_cxml), None)
@@ -285,9 +282,7 @@ def series_fixture(self, SeriesCollection_, series_collection_):
chart = Chart(chartSpace, None)
return chart, SeriesCollection_, plotArea, series_collection_
- @pytest.fixture(
- params=[("c:chartSpace/c:style{val=42}", 42), ("c:chartSpace", None)]
- )
+ @pytest.fixture(params=[("c:chartSpace/c:style{val=42}", 42), ("c:chartSpace", None)])
def style_get_fixture(self, request):
chartSpace_cxml, expected_value = request.param
chart = Chart(element(chartSpace_cxml), None)
@@ -341,9 +336,7 @@ def val_ax_raise_fixture(self):
@pytest.fixture
def CategoryAxis_(self, request, category_axis_):
- return class_mock(
- request, "pptx.chart.chart.CategoryAxis", return_value=category_axis_
- )
+ return class_mock(request, "pptx.chart.chart.CategoryAxis", return_value=category_axis_)
@pytest.fixture
def category_axis_(self, request):
@@ -355,9 +348,7 @@ def chart_data_(self, request):
@pytest.fixture
def ChartTitle_(self, request, chart_title_):
- return class_mock(
- request, "pptx.chart.chart.ChartTitle", return_value=chart_title_
- )
+ return class_mock(request, "pptx.chart.chart.ChartTitle", return_value=chart_title_)
@pytest.fixture
def chart_title_(self, request):
@@ -430,9 +421,7 @@ def series_rewriter_(self, request):
@pytest.fixture
def ValueAxis_(self, request, value_axis_):
- return class_mock(
- request, "pptx.chart.chart.ValueAxis", return_value=value_axis_
- )
+ return class_mock(request, "pptx.chart.chart.ValueAxis", return_value=value_axis_)
@pytest.fixture
def value_axis_(self, request):
@@ -497,20 +486,17 @@ def has_tf_get_fixture(self, request):
(
"c:title{a:b=c}",
True,
- "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr"
- ")",
+ "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr" ")",
),
(
"c:title{a:b=c}/c:tx",
True,
- "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr"
- ")",
+ "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr" ")",
),
(
"c:title{a:b=c}/c:tx/c:strRef",
True,
- "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr"
- ")",
+ "c:title{a:b=c}/c:tx/c:rich/(a:bodyPr,a:lstStyle,a:p/a:pPr/a:defRPr" ")",
),
("c:title/c:tx/c:rich", True, "c:title/c:tx/c:rich"),
("c:title", False, "c:title"),
@@ -594,9 +580,7 @@ def chart_(self, request):
@pytest.fixture
def PlotFactory_(self, request, plot_):
- return function_mock(
- request, "pptx.chart.chart.PlotFactory", return_value=plot_
- )
+ return function_mock(request, "pptx.chart.chart.PlotFactory", return_value=plot_)
@pytest.fixture
def plot_(self, request):
diff --git a/tests/chart/test_data.py b/tests/chart/test_data.py
index 10b325bf9..9b6097020 100644
--- a/tests/chart/test_data.py
+++ b/tests/chart/test_data.py
@@ -1,19 +1,14 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
-"""
-Test suite for pptx.chart.data module
-"""
+"""Test suite for `pptx.chart.data` module."""
-from __future__ import absolute_import, print_function, unicode_literals
+from __future__ import annotations
from datetime import date, datetime
import pytest
from pptx.chart.data import (
- _BaseChartData,
- _BaseDataPoint,
- _BaseSeriesData,
BubbleChartData,
BubbleDataPoint,
BubbleSeriesData,
@@ -26,11 +21,14 @@
XyChartData,
XyDataPoint,
XySeriesData,
+ _BaseChartData,
+ _BaseDataPoint,
+ _BaseSeriesData,
)
from pptx.chart.xlsx import CategoryWorkbookWriter
from pptx.enum.chart import XL_CHART_TYPE
-from ..unitutil.mock import call, class_mock, instance_mock, property_mock
+from ..unitutil.mock import Mock, call, class_mock, instance_mock, property_mock
class DescribeChartData(object):
@@ -39,12 +37,16 @@ def it_is_a_CategoryChartData_object(self):
class Describe_BaseChartData(object):
- def it_can_generate_chart_part_XML_for_its_data(self, xml_bytes_fixture):
- chart_data, chart_type_, ChartXmlWriter_, expected_bytes = xml_bytes_fixture
- xml_bytes = chart_data.xml_bytes(chart_type_)
+ """Unit-test suite for `pptx.chart.data._BaseChartData`."""
- ChartXmlWriter_.assert_called_once_with(chart_type_, chart_data)
- assert xml_bytes == expected_bytes
+ def it_can_generate_chart_part_XML_for_its_data(self, ChartXmlWriter_: Mock):
+ ChartXmlWriter_.return_value.xml = "ƒøØßår"
+ chart_data = _BaseChartData()
+
+ xml_bytes = chart_data.xml_bytes(XL_CHART_TYPE.PIE)
+
+ ChartXmlWriter_.assert_called_once_with(XL_CHART_TYPE.PIE, chart_data)
+ assert xml_bytes == "ƒøØßår".encode("utf-8")
def it_knows_its_number_format(self, number_format_fixture):
chart_data, expected_value = number_format_fixture
@@ -59,12 +61,6 @@ def number_format_fixture(self, request):
chart_data = _BaseChartData(*argv)
return chart_data, expected_value
- @pytest.fixture
- def xml_bytes_fixture(self, chart_type_, ChartXmlWriter_):
- chart_data = _BaseChartData()
- expected_bytes = "ƒøØßår".encode("utf-8")
- return chart_data, chart_type_, ChartXmlWriter_, expected_bytes
-
# fixture components ---------------------------------------------
@pytest.fixture
@@ -73,10 +69,6 @@ def ChartXmlWriter_(self, request):
ChartXmlWriter_.return_value.xml = "ƒøØßår"
return ChartXmlWriter_
- @pytest.fixture
- def chart_type_(self):
- return XL_CHART_TYPE.PIE
-
class Describe_BaseSeriesData(object):
def it_knows_its_name(self, name_fixture):
diff --git a/tests/chart/test_datalabel.py b/tests/chart/test_datalabel.py
index 19eddcc6f..ad02efc10 100644
--- a/tests/chart/test_datalabel.py
+++ b/tests/chart/test_datalabel.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
-
"""Unit test suite for the pptx.chart.datalabel module"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
@@ -279,9 +277,7 @@ def it_can_change_its_number_format(self, number_format_set_fixture):
data_labels.number_format = new_value
assert data_labels._element.xml == expected_xml
- def it_knows_whether_its_number_format_is_linked(
- self, number_format_is_linked_get_fixture
- ):
+ def it_knows_whether_its_number_format_is_linked(self, number_format_is_linked_get_fixture):
data_labels, expected_value = number_format_is_linked_get_fixture
assert data_labels.number_format_is_linked is expected_value
diff --git a/tests/chart/test_legend.py b/tests/chart/test_legend.py
index 1624dc6d6..d77cd9f37 100644
--- a/tests/chart/test_legend.py
+++ b/tests/chart/test_legend.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for `pptx.chart.legend` module."""
-"""
-Test suite for pptx.chart.legend module
-"""
-
-from __future__ import absolute_import, print_function
+from __future__ import annotations
import pytest
@@ -32,15 +28,11 @@ def it_can_change_its_horizontal_offset(self, horz_offset_set_fixture):
legend.horz_offset = new_value
assert legend._element.xml == expected_xml
- def it_knows_whether_it_should_overlap_the_chart(
- self, include_in_layout_get_fixture
- ):
+ def it_knows_whether_it_should_overlap_the_chart(self, include_in_layout_get_fixture):
legend, expected_value = include_in_layout_get_fixture
assert legend.include_in_layout == expected_value
- def it_can_change_whether_it_overlaps_the_chart(
- self, include_in_layout_set_fixture
- ):
+ def it_can_change_whether_it_overlaps_the_chart(self, include_in_layout_set_fixture):
legend, new_value, expected_xml = include_in_layout_set_fixture
legend.include_in_layout = new_value
assert legend._element.xml == expected_xml
@@ -80,8 +72,7 @@ def font_fixture(self, request):
("c:legend/c:layout/c:manualLayout/c:xMode{val=factor}", 0.0),
("c:legend/c:layout/c:manualLayout/(c:xMode,c:x{val=0.42})", 0.42),
(
- "c:legend/c:layout/c:manualLayout/(c:xMode{val=factor},c:x{val=0.42"
- "})",
+ "c:legend/c:layout/c:manualLayout/(c:xMode{val=factor},c:x{val=0.42" "})",
0.42,
),
]
diff --git a/tests/chart/test_marker.py b/tests/chart/test_marker.py
index 4bbe22cb4..b9a8f3c5d 100644
--- a/tests/chart/test_marker.py
+++ b/tests/chart/test_marker.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for the `pptx.chart.marker` module."""
-"""
-Unit test suite for the pptx.chart.marker module.
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
@@ -131,9 +127,7 @@ def style_set_fixture(self, request):
@pytest.fixture
def ChartFormat_(self, request, chart_format_):
- return class_mock(
- request, "pptx.chart.marker.ChartFormat", return_value=chart_format_
- )
+ return class_mock(request, "pptx.chart.marker.ChartFormat", return_value=chart_format_)
@pytest.fixture
def chart_format_(self, request):
diff --git a/tests/chart/test_plot.py b/tests/chart/test_plot.py
index 3a9e9f136..7e0f75e2d 100644
--- a/tests/chart/test_plot.py
+++ b/tests/chart/test_plot.py
@@ -1,19 +1,16 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
-"""
-Test suite for pptx.chart.plot module
-"""
+"""Unit-test suite for `pptx.chart.plot` module."""
-from __future__ import absolute_import, print_function
+from __future__ import annotations
import pytest
from pptx.chart.category import Categories
from pptx.chart.chart import Chart
from pptx.chart.plot import (
- _BasePlot,
- AreaPlot,
Area3DPlot,
+ AreaPlot,
BarPlot,
BubblePlot,
DataLabels,
@@ -24,6 +21,7 @@
PlotTypeInspector,
RadarPlot,
XyPlot,
+ _BasePlot,
)
from pptx.chart.series import SeriesCollection
from pptx.enum.chart import XL_CHART_TYPE as XL
@@ -46,15 +44,11 @@ def it_can_change_whether_it_has_data_labels(self, has_data_labels_set_fixture):
plot.has_data_labels = new_value
assert plot._element.xml == expected_xml
- def it_knows_whether_it_varies_color_by_category(
- self, vary_by_categories_get_fixture
- ):
+ def it_knows_whether_it_varies_color_by_category(self, vary_by_categories_get_fixture):
plot, expected_value = vary_by_categories_get_fixture
assert plot.vary_by_categories == expected_value
- def it_can_change_whether_it_varies_color_by_category(
- self, vary_by_categories_set_fixture
- ):
+ def it_can_change_whether_it_varies_color_by_category(self, vary_by_categories_set_fixture):
plot, new_value, expected_xml = vary_by_categories_set_fixture
plot.vary_by_categories = new_value
assert plot._element.xml == expected_xml
@@ -176,9 +170,7 @@ def vary_by_categories_set_fixture(self, request):
@pytest.fixture
def Categories_(self, request, categories_):
- return class_mock(
- request, "pptx.chart.plot.Categories", return_value=categories_
- )
+ return class_mock(request, "pptx.chart.plot.Categories", return_value=categories_)
@pytest.fixture
def categories_(self, request):
@@ -190,9 +182,7 @@ def chart_(self, request):
@pytest.fixture
def DataLabels_(self, request, data_labels_):
- return class_mock(
- request, "pptx.chart.plot.DataLabels", return_value=data_labels_
- )
+ return class_mock(request, "pptx.chart.plot.DataLabels", return_value=data_labels_)
@pytest.fixture
def data_labels_(self, request):
@@ -430,21 +420,18 @@ def it_can_determine_the_chart_type_of_a_plot(self, chart_type_fixture):
("c:lineChart/c:grouping{val=percentStacked}", XL.LINE_MARKERS_STACKED_100),
("c:lineChart/c:ser/c:marker/c:symbol{val=none}", XL.LINE),
(
- "c:lineChart/(c:grouping{val=stacked},c:ser/c:marker/c:symbol{val=n"
- "one})",
+ "c:lineChart/(c:grouping{val=stacked},c:ser/c:marker/c:symbol{val=n" "one})",
XL.LINE_STACKED,
),
(
- "c:lineChart/(c:grouping{val=percentStacked},c:ser/c:marker/c:symbo"
- "l{val=none})",
+ "c:lineChart/(c:grouping{val=percentStacked},c:ser/c:marker/c:symbo" "l{val=none})",
XL.LINE_STACKED_100,
),
("c:pieChart", XL.PIE),
("c:pieChart/c:ser/c:explosion{val=25}", XL.PIE_EXPLODED),
("c:scatterChart/c:scatterStyle", XL.XY_SCATTER),
(
- "c:scatterChart/(c:scatterStyle{val=lineMarker},c:ser/c:spPr/a:ln/a"
- ":noFill)",
+ "c:scatterChart/(c:scatterStyle{val=lineMarker},c:ser/c:spPr/a:ln/a" ":noFill)",
XL.XY_SCATTER,
),
("c:scatterChart/c:scatterStyle{val=lineMarker}", XL.XY_SCATTER_LINES),
@@ -473,8 +460,7 @@ def it_can_determine_the_chart_type_of_a_plot(self, chart_type_fixture):
("c:radarChart/c:radarStyle{val=marker}", XL.RADAR_MARKERS),
("c:radarChart/c:radarStyle{val=filled}", XL.RADAR_FILLED),
(
- "c:radarChart/(c:radarStyle{val=marker},c:ser/c:marker/c:symbol{val"
- "=none})",
+ "c:radarChart/(c:radarStyle{val=marker},c:ser/c:marker/c:symbol{val" "=none})",
XL.RADAR,
),
]
diff --git a/tests/chart/test_point.py b/tests/chart/test_point.py
index cba2eb0bc..8e00d9675 100644
--- a/tests/chart/test_point.py
+++ b/tests/chart/test_point.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for the `pptx.chart.point` module."""
-"""
-Unit test suite for the pptx.chart.point module.
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
@@ -166,9 +162,7 @@ def marker_fixture(self, Marker_, marker_):
@pytest.fixture
def ChartFormat_(self, request, chart_format_):
- return class_mock(
- request, "pptx.chart.point.ChartFormat", return_value=chart_format_
- )
+ return class_mock(request, "pptx.chart.point.ChartFormat", return_value=chart_format_)
@pytest.fixture
def chart_format_(self, request):
@@ -176,9 +170,7 @@ def chart_format_(self, request):
@pytest.fixture
def DataLabel_(self, request, data_label_):
- return class_mock(
- request, "pptx.chart.point.DataLabel", return_value=data_label_
- )
+ return class_mock(request, "pptx.chart.point.DataLabel", return_value=data_label_)
@pytest.fixture
def data_label_(self, request):
diff --git a/tests/chart/test_series.py b/tests/chart/test_series.py
index 35fa2425d..9a60351e1 100644
--- a/tests/chart/test_series.py
+++ b/tests/chart/test_series.py
@@ -1,8 +1,8 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
-"""Test suite for pptx.chart.series module."""
+"""Unit-test suite for `pptx.chart.series` module."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
@@ -12,16 +12,16 @@
from pptx.chart.series import (
AreaSeries,
BarSeries,
- _BaseCategorySeries,
- _BaseSeries,
BubbleSeries,
LineSeries,
- _MarkerMixin,
PieSeries,
RadarSeries,
SeriesCollection,
- _SeriesFactory,
XySeries,
+ _BaseCategorySeries,
+ _BaseSeries,
+ _MarkerMixin,
+ _SeriesFactory,
)
from pptx.dml.chtfmt import ChartFormat
@@ -73,9 +73,7 @@ def name_fixture(self, request):
@pytest.fixture
def ChartFormat_(self, request, chart_format_):
- return class_mock(
- request, "pptx.chart.series.ChartFormat", return_value=chart_format_
- )
+ return class_mock(request, "pptx.chart.series.ChartFormat", return_value=chart_format_)
@pytest.fixture
def chart_format_(self, request):
@@ -87,9 +85,7 @@ def it_is_a_BaseSeries_subclass(self, subclass_fixture):
base_category_series = subclass_fixture
assert isinstance(base_category_series, _BaseSeries)
- def it_provides_access_to_its_data_labels(
- self, data_labels_fixture, DataLabels_, data_labels_
- ):
+ def it_provides_access_to_its_data_labels(self, data_labels_fixture, DataLabels_, data_labels_):
ser, expected_dLbls_xml = data_labels_fixture
DataLabels_.return_value = data_labels_
series = _BaseCategorySeries(ser)
@@ -148,8 +144,7 @@ def subclass_fixture(self):
("c:ser/c:val/c:numRef/c:numCache", ()),
("c:ser/c:val/c:numRef/c:numCache/c:ptCount{val=0}", ()),
(
- 'c:ser/c:val/c:numRef/c:numCache/(c:ptCount{val=1},c:pt{idx=0}/c:v"'
- '1.1")',
+ 'c:ser/c:val/c:numRef/c:numCache/(c:ptCount{val=1},c:pt{idx=0}/c:v"' '1.1")',
(1.1,),
),
(
@@ -178,9 +173,7 @@ def values_get_fixture(self, request):
@pytest.fixture
def CategoryPoints_(self, request, points_):
- return class_mock(
- request, "pptx.chart.series.CategoryPoints", return_value=points_
- )
+ return class_mock(request, "pptx.chart.series.CategoryPoints", return_value=points_)
@pytest.fixture
def DataLabels_(self, request):
@@ -238,15 +231,11 @@ def it_is_a_BaseCategorySeries_subclass(self, subclass_fixture):
bar_series = subclass_fixture
assert isinstance(bar_series, _BaseCategorySeries)
- def it_knows_whether_it_should_invert_if_negative(
- self, invert_if_negative_get_fixture
- ):
+ def it_knows_whether_it_should_invert_if_negative(self, invert_if_negative_get_fixture):
bar_series, expected_value = invert_if_negative_get_fixture
assert bar_series.invert_if_negative == expected_value
- def it_can_change_whether_it_inverts_if_negative(
- self, invert_if_negative_set_fixture
- ):
+ def it_can_change_whether_it_inverts_if_negative(self, invert_if_negative_set_fixture):
bar_series, new_value, expected_xml = invert_if_negative_set_fixture
bar_series.invert_if_negative = new_value
assert bar_series._element.xml == expected_xml
@@ -312,9 +301,7 @@ def points_fixture(self, BubblePoints_, points_):
@pytest.fixture
def BubblePoints_(self, request, points_):
- return class_mock(
- request, "pptx.chart.series.BubblePoints", return_value=points_
- )
+ return class_mock(request, "pptx.chart.series.BubblePoints", return_value=points_)
@pytest.fixture
def points_(self, request):
@@ -433,8 +420,7 @@ def subclass_fixture(self):
("c:ser/c:yVal/c:numRef", ()),
("c:ser/c:val/c:numRef/c:numCache", ()),
(
- "c:ser/c:yVal/c:numRef/c:numCache/(c:ptCount{val=1},c:pt{idx=0}/c:v"
- '"1.1")',
+ "c:ser/c:yVal/c:numRef/c:numCache/(c:ptCount{val=1},c:pt{idx=0}/c:v" '"1.1")',
(1.1,),
),
(
@@ -483,8 +469,7 @@ def it_supports_len(self, len_fixture):
params=[
("c:barChart/c:ser/c:order{val=42}", 0, 0),
(
- "c:barChart/(c:ser/c:order{val=9},c:ser/c:order{val=6},c:ser/c:orde"
- "r{val=3})",
+ "c:barChart/(c:ser/c:order{val=9},c:ser/c:order{val=6},c:ser/c:orde" "r{val=3})",
2,
0,
),
@@ -509,8 +494,7 @@ def getitem_fixture(self, request, _SeriesFactory_, series_):
("c:barChart", 0),
("c:barChart/c:ser/c:order{val=4}", 1),
(
- "c:barChart/(c:ser/c:order{val=4},c:ser/c:order{val=1},c:ser/c:orde"
- "r{val=6})",
+ "c:barChart/(c:ser/c:order{val=4},c:ser/c:order{val=1},c:ser/c:orde" "r{val=6})",
3,
),
("c:plotArea/c:barChart", 0),
@@ -531,9 +515,7 @@ def len_fixture(self, request):
@pytest.fixture
def _SeriesFactory_(self, request, series_):
- return function_mock(
- request, "pptx.chart.series._SeriesFactory", return_value=series_
- )
+ return function_mock(request, "pptx.chart.series._SeriesFactory", return_value=series_)
@pytest.fixture
def series_(self, request):
diff --git a/tests/chart/test_xlsx.py b/tests/chart/test_xlsx.py
index ec96c9e02..dde9d4d53 100644
--- a/tests/chart/test_xlsx.py
+++ b/tests/chart/test_xlsx.py
@@ -1,9 +1,12 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.chart.xlsx` module."""
-import pytest
+from __future__ import annotations
+
+import io
+import pytest
from xlsxwriter import Workbook
from xlsxwriter.worksheet import Worksheet
@@ -15,12 +18,11 @@
XyChartData,
)
from pptx.chart.xlsx import (
- _BaseWorkbookWriter,
BubbleWorkbookWriter,
CategoryWorkbookWriter,
XyWorkbookWriter,
+ _BaseWorkbookWriter,
)
-from pptx.compat import BytesIO
from ..unitutil.mock import ANY, call, class_mock, instance_mock, method_mock
@@ -31,9 +33,7 @@ class Describe_BaseWorkbookWriter(object):
def it_can_generate_a_chart_data_Excel_blob(
self, request, xlsx_file_, workbook_, worksheet_, BytesIO_
):
- _populate_worksheet_ = method_mock(
- request, _BaseWorkbookWriter, "_populate_worksheet"
- )
+ _populate_worksheet_ = method_mock(request, _BaseWorkbookWriter, "_populate_worksheet")
_open_worksheet_ = method_mock(request, _BaseWorkbookWriter, "_open_worksheet")
# --- to make context manager behavior work ---
_open_worksheet_.return_value.__enter__.return_value = (workbook_, worksheet_)
@@ -44,9 +44,7 @@ def it_can_generate_a_chart_data_Excel_blob(
xlsx_blob = workbook_writer.xlsx_blob
_open_worksheet_.assert_called_once_with(workbook_writer, xlsx_file_)
- _populate_worksheet_.assert_called_once_with(
- workbook_writer, workbook_, worksheet_
- )
+ _populate_worksheet_.assert_called_once_with(workbook_writer, workbook_, worksheet_)
assert xlsx_blob == b"xlsx-blob"
def it_can_open_a_worksheet_in_a_context(self, open_fixture):
@@ -81,7 +79,7 @@ def populate_fixture(self):
@pytest.fixture
def BytesIO_(self, request):
- return class_mock(request, "pptx.chart.xlsx.BytesIO")
+ return class_mock(request, "pptx.chart.xlsx.io.BytesIO")
@pytest.fixture
def Workbook_(self, request, workbook_):
@@ -97,7 +95,7 @@ def worksheet_(self, request):
@pytest.fixture
def xlsx_file_(self, request):
- return instance_mock(request, BytesIO)
+ return instance_mock(request, io.BytesIO)
class DescribeCategoryWorkbookWriter(object):
@@ -207,9 +205,7 @@ def col_ref_fixture(self, request):
return column_number, expected_value
@pytest.fixture
- def populate_fixture(
- self, workbook_, worksheet_, _write_categories_, _write_series_
- ):
+ def populate_fixture(self, workbook_, worksheet_, _write_categories_, _write_series_):
workbook_writer = CategoryWorkbookWriter(None)
return workbook_writer, workbook_, worksheet_
@@ -293,9 +289,7 @@ def write_cats_fixture(
return workbook_writer, workbook_, worksheet_, number_format, calls
@pytest.fixture
- def write_sers_fixture(
- self, request, chart_data_, workbook_, worksheet_, categories_
- ):
+ def write_sers_fixture(self, request, chart_data_, workbook_, worksheet_, categories_):
workbook_writer = CategoryWorkbookWriter(chart_data_)
num_format = workbook_.add_format.return_value
calls = [call.write(0, 1, "S1"), call.write_column(1, 1, (42, 24), num_format)]
@@ -330,21 +324,15 @@ def worksheet_(self, request):
@pytest.fixture
def _write_cat_column_(self, request):
- return method_mock(
- request, CategoryWorkbookWriter, "_write_cat_column", autospec=True
- )
+ return method_mock(request, CategoryWorkbookWriter, "_write_cat_column", autospec=True)
@pytest.fixture
def _write_categories_(self, request):
- return method_mock(
- request, CategoryWorkbookWriter, "_write_categories", autospec=True
- )
+ return method_mock(request, CategoryWorkbookWriter, "_write_categories", autospec=True)
@pytest.fixture
def _write_series_(self, request):
- return method_mock(
- request, CategoryWorkbookWriter, "_write_series", autospec=True
- )
+ return method_mock(request, CategoryWorkbookWriter, "_write_series", autospec=True)
class DescribeBubbleWorkbookWriter(object):
diff --git a/tests/chart/test_xmlwriter.py b/tests/chart/test_xmlwriter.py
index 19e7e6473..bb7354983 100644
--- a/tests/chart/test_xmlwriter.py
+++ b/tests/chart/test_xmlwriter.py
@@ -1,10 +1,8 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
-"""
-Test suite for pptx.chart.xmlwriter module
-"""
+"""Unit-test suite for `pptx.chart.xmlwriter` module."""
-from __future__ import absolute_import, print_function, unicode_literals
+from __future__ import annotations
from datetime import date
from itertools import islice
@@ -12,14 +10,16 @@
import pytest
from pptx.chart.data import (
- _BaseChartData,
- _BaseSeriesData,
BubbleChartData,
CategoryChartData,
CategorySeriesData,
XyChartData,
+ _BaseChartData,
+ _BaseSeriesData,
)
from pptx.chart.xmlwriter import (
+ ChartXmlWriter,
+ SeriesXmlRewriterFactory,
_AreaChartXmlWriter,
_BarChartXmlWriter,
_BaseSeriesXmlRewriter,
@@ -28,12 +28,10 @@
_BubbleSeriesXmlWriter,
_CategorySeriesXmlRewriter,
_CategorySeriesXmlWriter,
- ChartXmlWriter,
_DoughnutChartXmlWriter,
_LineChartXmlWriter,
_PieChartXmlWriter,
_RadarChartXmlWriter,
- SeriesXmlRewriterFactory,
_XyChartXmlWriter,
_XySeriesXmlRewriter,
_XySeriesXmlWriter,
@@ -292,9 +290,7 @@ class Describe_PieChartXmlWriter(object):
("PIE_EXPLODED", 3, 1, "3x1-pie-exploded"),
),
)
- def it_can_generate_xml_for_a_pie_chart(
- self, enum_member, cat_count, ser_count, snippet_name
- ):
+ def it_can_generate_xml_for_a_pie_chart(self, enum_member, cat_count, ser_count, snippet_name):
chart_type = getattr(XL_CHART_TYPE, enum_member)
chart_data = make_category_chart_data(cat_count, str, ser_count)
xml_writer = _PieChartXmlWriter(chart_type, chart_data)
@@ -306,9 +302,7 @@ class Describe_RadarChartXmlWriter(object):
"""Unit-test suite for `pptx.chart.xmlwriter._RadarChartXmlWriter`."""
def it_can_generate_xml_for_a_radar_chart(self):
- series_data_seq = make_category_chart_data(
- cat_count=5, cat_type=str, ser_count=2
- )
+ series_data_seq = make_category_chart_data(cat_count=5, cat_type=str, ser_count=2)
xml_writer = _RadarChartXmlWriter(XL_CHART_TYPE.RADAR, series_data_seq)
assert xml_writer.xml == snippet_text("2x5-radar")
@@ -456,9 +450,7 @@ class Describe_BaseSeriesXmlRewriter(object):
def it_can_replace_series_data(self, replace_fixture):
rewriter, chartSpace, plotArea, ser_count, calls = replace_fixture
rewriter.replace_series_data(chartSpace)
- rewriter._adjust_ser_count.assert_called_once_with(
- rewriter, plotArea, ser_count
- )
+ rewriter._adjust_ser_count.assert_called_once_with(rewriter, plotArea, ser_count)
assert rewriter._rewrite_ser_data.call_args_list == calls
def it_adjusts_the_ser_count_to_help(self, adjust_fixture):
@@ -519,9 +511,7 @@ def clone_fixture(self, request):
return rewriter, plotArea, count, expected_xml
@pytest.fixture
- def replace_fixture(
- self, request, chart_data_, _adjust_ser_count_, _rewrite_ser_data_
- ):
+ def replace_fixture(self, request, chart_data_, _adjust_ser_count_, _rewrite_ser_data_):
rewriter = _BaseSeriesXmlRewriter(chart_data_)
chartSpace = element(
"c:chartSpace/c:chart/c:plotArea/c:barChart/(c:ser/c:order{val=0"
@@ -572,15 +562,11 @@ def trim_fixture(self, request):
@pytest.fixture
def _add_cloned_sers_(self, request):
- return method_mock(
- request, _BaseSeriesXmlRewriter, "_add_cloned_sers", autospec=True
- )
+ return method_mock(request, _BaseSeriesXmlRewriter, "_add_cloned_sers", autospec=True)
@pytest.fixture
def _adjust_ser_count_(self, request):
- return method_mock(
- request, _BaseSeriesXmlRewriter, "_adjust_ser_count", autospec=True
- )
+ return method_mock(request, _BaseSeriesXmlRewriter, "_adjust_ser_count", autospec=True)
@pytest.fixture
def chart_data_(self, request):
@@ -588,15 +574,11 @@ def chart_data_(self, request):
@pytest.fixture
def _rewrite_ser_data_(self, request):
- return method_mock(
- request, _BaseSeriesXmlRewriter, "_rewrite_ser_data", autospec=True
- )
+ return method_mock(request, _BaseSeriesXmlRewriter, "_rewrite_ser_data", autospec=True)
@pytest.fixture
def _trim_ser_count_by_(self, request):
- return method_mock(
- request, _BaseSeriesXmlRewriter, "_trim_ser_count_by", autospec=True
- )
+ return method_mock(request, _BaseSeriesXmlRewriter, "_trim_ser_count_by", autospec=True)
class Describe_BubbleSeriesXmlRewriter(object):
diff --git a/tests/dml/test_chtfmt.py b/tests/dml/test_chtfmt.py
index 42b90f498..f87752180 100644
--- a/tests/dml/test_chtfmt.py
+++ b/tests/dml/test_chtfmt.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for `pptx.dml.chtfmt` module."""
-"""
-Unit test suite for the pptx.dml.chtfmt module.
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
diff --git a/tests/dml/test_color.py b/tests/dml/test_color.py
index f0c536340..95a1f7c5d 100644
--- a/tests/dml/test_color.py
+++ b/tests/dml/test_color.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for `pptx.text` module."""
-"""
-Test suite for pptx.text module.
-"""
-
-from __future__ import absolute_import
+from __future__ import annotations
import pytest
@@ -182,9 +178,7 @@ def set_brightness_fixture_(self, request):
"-0.3 to -0.4": (an_srgbClr, 70000, None, -0.4, 60000, None),
"-0.4 to 0": (a_sysClr, 60000, None, 0, None, None),
}
- xClr_bldr_fn, mod_in, off_in, brightness, mod_out, off_out = mapping[
- request.param
- ]
+ xClr_bldr_fn, mod_in, off_in, brightness, mod_out, off_out = mapping[request.param]
xClr_bldr = xClr_bldr_fn()
if mod_in is not None:
@@ -222,10 +216,7 @@ def set_rgb_fixture_(self, request):
color_format = ColorFormat.from_colorchoice_parent(solidFill)
rgb_color = RGBColor(0x12, 0x34, 0x56)
expected_xml = (
- a_solidFill()
- .with_nsdecls()
- .with_child(an_srgbClr().with_val("123456"))
- .xml()
+ a_solidFill().with_nsdecls().with_child(an_srgbClr().with_val("123456")).xml()
)
return color_format, rgb_color, expected_xml
@@ -248,10 +239,7 @@ def set_theme_color_fixture_(self, request):
color_format = ColorFormat.from_colorchoice_parent(solidFill)
theme_color = MSO_THEME_COLOR.ACCENT_6
expected_xml = (
- a_solidFill()
- .with_nsdecls()
- .with_child(a_schemeClr().with_val("accent6"))
- .xml()
+ a_solidFill().with_nsdecls().with_child(a_schemeClr().with_val("accent6")).xml()
)
return color_format, theme_color, expected_xml
diff --git a/tests/dml/test_effect.py b/tests/dml/test_effect.py
index 53e2106de..1907e561d 100644
--- a/tests/dml/test_effect.py
+++ b/tests/dml/test_effect.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for `pptx.dml.effect` module."""
-"""Test suite for pptx.dml.effect module."""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
diff --git a/tests/dml/test_fill.py b/tests/dml/test_fill.py
index 2c2af4e03..defbaf980 100644
--- a/tests/dml/test_fill.py
+++ b/tests/dml/test_fill.py
@@ -1,14 +1,16 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.dml.fill` module."""
+from __future__ import annotations
+
import pytest
from pptx.dml.color import ColorFormat
from pptx.dml.fill import (
+ FillFormat,
_BlipFill,
_Fill,
- FillFormat,
_GradFill,
_GradientStop,
_GradientStops,
@@ -94,9 +96,7 @@ def it_can_change_the_angle_of_a_linear_gradient(self, grad_fill_, type_prop_):
assert grad_fill_.gradient_angle == 42.24
- def it_provides_access_to_the_gradient_stops(
- self, type_prop_, grad_fill_, gradient_stops_
- ):
+ def it_provides_access_to_the_gradient_stops(self, type_prop_, grad_fill_, gradient_stops_):
type_prop_.return_value = MSO_FILL.GRADIENT
grad_fill_.gradient_stops = gradient_stops_
fill = FillFormat(None, grad_fill_)
@@ -618,9 +618,7 @@ def pattern_set_fixture(self, request):
@pytest.fixture
def ColorFormat_from_colorchoice_parent_(self, request):
- return method_mock(
- request, ColorFormat, "from_colorchoice_parent", autospec=False
- )
+ return method_mock(request, ColorFormat, "from_colorchoice_parent", autospec=False)
@pytest.fixture
def color_(self, request):
@@ -662,9 +660,7 @@ def fore_color_fixture(self, ColorFormat_from_colorchoice_parent_, color_):
@pytest.fixture
def ColorFormat_from_colorchoice_parent_(self, request):
- return method_mock(
- request, ColorFormat, "from_colorchoice_parent", autospec=False
- )
+ return method_mock(request, ColorFormat, "from_colorchoice_parent", autospec=False)
@pytest.fixture
def color_(self, request):
diff --git a/tests/dml/test_line.py b/tests/dml/test_line.py
index b33e6e094..158e55589 100644
--- a/tests/dml/test_line.py
+++ b/tests/dml/test_line.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Test suite for `pptx.dml.line` module."""
-"""
-Test suite for pptx.dml.line module
-"""
-
-from __future__ import absolute_import, print_function, unicode_literals
+from __future__ import annotations
import pytest
@@ -25,10 +21,39 @@ def it_knows_its_dash_style(self, dash_style_get_fixture):
line, expected_value = dash_style_get_fixture
assert line.dash_style == expected_value
- def it_can_change_its_dash_style(self, dash_style_set_fixture):
- line, dash_style, spPr, expected_xml = dash_style_set_fixture
+ @pytest.mark.parametrize(
+ ("spPr_cxml", "dash_style", "expected_cxml"),
+ [
+ ("p:spPr{a:b=c}", MSO_LINE.DASH, "p:spPr{a:b=c}/a:ln/a:prstDash{val=dash}"),
+ ("p:spPr/a:ln", MSO_LINE.ROUND_DOT, "p:spPr/a:ln/a:prstDash{val=sysDot}"),
+ (
+ "p:spPr/a:ln/a:prstDash",
+ MSO_LINE.SOLID,
+ "p:spPr/a:ln/a:prstDash{val=solid}",
+ ),
+ (
+ "p:spPr/a:ln/a:custDash",
+ MSO_LINE.DASH_DOT,
+ "p:spPr/a:ln/a:prstDash{val=dashDot}",
+ ),
+ (
+ "p:spPr/a:ln/a:prstDash{val=dash}",
+ MSO_LINE.LONG_DASH,
+ "p:spPr/a:ln/a:prstDash{val=lgDash}",
+ ),
+ ("p:spPr/a:ln/a:prstDash{val=dash}", None, "p:spPr/a:ln"),
+ ("p:spPr/a:ln/a:custDash", None, "p:spPr/a:ln"),
+ ],
+ )
+ def it_can_change_its_dash_style(
+ self, spPr_cxml: str, dash_style: MSO_LINE, expected_cxml: str
+ ):
+ spPr = element(spPr_cxml)
+ line = LineFormat(spPr)
+
line.dash_style = dash_style
- assert spPr.xml == expected_xml
+
+ assert spPr.xml == xml(expected_cxml)
def it_knows_its_width(self, width_get_fixture):
line, expected_line_width = width_get_fixture
@@ -75,36 +100,6 @@ def dash_style_get_fixture(self, request):
line = LineFormat(spPr)
return line, expected_value
- @pytest.fixture(
- params=[
- ("p:spPr{a:b=c}", MSO_LINE.DASH, "p:spPr{a:b=c}/a:ln/a:prstDash{val=dash}"),
- ("p:spPr/a:ln", MSO_LINE.ROUND_DOT, "p:spPr/a:ln/a:prstDash{val=sysDot}"),
- (
- "p:spPr/a:ln/a:prstDash",
- MSO_LINE.SOLID,
- "p:spPr/a:ln/a:prstDash{val=solid}",
- ),
- (
- "p:spPr/a:ln/a:custDash",
- MSO_LINE.DASH_DOT,
- "p:spPr/a:ln/a:prstDash{val=dashDot}",
- ),
- (
- "p:spPr/a:ln/a:prstDash{val=dash}",
- MSO_LINE.LONG_DASH,
- "p:spPr/a:ln/a:prstDash{val=lgDash}",
- ),
- ("p:spPr/a:ln/a:prstDash{val=dash}", None, "p:spPr/a:ln"),
- ("p:spPr/a:ln/a:custDash", None, "p:spPr/a:ln"),
- ]
- )
- def dash_style_set_fixture(self, request):
- spPr_cxml, dash_style, expected_cxml = request.param
- spPr = element(spPr_cxml)
- line = LineFormat(spPr)
- expected_xml = xml(expected_cxml)
- return line, dash_style, spPr, expected_xml
-
@pytest.fixture
def fill_fixture(self, line, FillFormat_, ln_, fill_):
return line, FillFormat_, ln_, fill_
diff --git a/tests/opc/test_oxml.py b/tests/opc/test_oxml.py
index f68c6857a..5dee9408b 100644
--- a/tests/opc/test_oxml.py
+++ b/tests/opc/test_oxml.py
@@ -1,8 +1,8 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.opc.oxml` module."""
-from __future__ import unicode_literals
+from __future__ import annotations
+
+from typing import cast
import pytest
@@ -13,141 +13,179 @@
CT_Relationship,
CT_Relationships,
CT_Types,
+ nsmap,
oxml_tostring,
serialize_part_xml,
)
+from pptx.opc.packuri import PackURI
from pptx.oxml import parse_xml
+from pptx.oxml.xmlchemy import BaseOxmlElement
-from .unitdata.rels import (
- a_Default,
- an_Override,
- a_Relationship,
- a_Relationships,
- a_Types,
-)
+from ..unitutil.cxml import element
-class DescribeCT_Default(object):
+class DescribeCT_Default:
"""Unit-test suite for `pptx.opc.oxml.CT_Default` objects."""
def it_provides_read_access_to_xml_values(self):
- default = a_Default().element
+ default = cast(CT_Default, element("ct:Default{Extension=xml,ContentType=application/xml}"))
assert default.extension == "xml"
assert default.contentType == "application/xml"
-class DescribeCT_Override(object):
+class DescribeCT_Override:
"""Unit-test suite for `pptx.opc.oxml.CT_Override` objects."""
def it_provides_read_access_to_xml_values(self):
- override = an_Override().element
+ override = cast(
+ CT_Override, element("ct:Override{PartName=/part/name.xml,ContentType=text/plain}")
+ )
assert override.partName == "/part/name.xml"
- assert override.contentType == "app/vnd.type"
+ assert override.contentType == "text/plain"
-class DescribeCT_Relationship(object):
+class DescribeCT_Relationship:
"""Unit-test suite for `pptx.opc.oxml.CT_Relationship` objects."""
def it_provides_read_access_to_xml_values(self):
- rel = a_Relationship().element
+ rel = cast(
+ CT_Relationship,
+ element("pr:Relationship{Id=rId9,Type=ReLtYpE,Target=docProps/core.xml}"),
+ )
assert rel.rId == "rId9"
assert rel.reltype == "ReLtYpE"
assert rel.target_ref == "docProps/core.xml"
assert rel.targetMode == RTM.INTERNAL
- def it_can_construct_from_attribute_values(self):
- cases = (
- ("rId9", "ReLtYpE", "foo/bar.xml", None),
- ("rId9", "ReLtYpE", "bar/foo.xml", RTM.INTERNAL),
- ("rId9", "ReLtYpE", "http://some/link", RTM.EXTERNAL),
+ def it_constructs_an_internal_relationship_when_no_target_mode_is_provided(self):
+ rel = CT_Relationship.new("rId9", "ReLtYpE", "foo/bar.xml")
+
+ assert rel.rId == "rId9"
+ assert rel.reltype == "ReLtYpE"
+ assert rel.target_ref == "foo/bar.xml"
+ assert rel.targetMode == RTM.INTERNAL
+ assert rel.xml == (
+ f''
)
- for rId, reltype, target, target_mode in cases:
- if target_mode is None:
- rel = CT_Relationship.new(rId, reltype, target)
- else:
- rel = CT_Relationship.new(rId, reltype, target, target_mode)
- builder = a_Relationship().with_target(target)
- if target_mode == RTM.EXTERNAL:
- builder = builder.with_target_mode(RTM.EXTERNAL)
- expected_rel_xml = builder.xml
- assert rel.xml == expected_rel_xml
-
-
-class DescribeCT_Relationships(object):
+
+ def and_it_constructs_an_internal_relationship_when_target_mode_INTERNAL_is_specified(self):
+ rel = CT_Relationship.new("rId9", "ReLtYpE", "foo/bar.xml", RTM.INTERNAL)
+
+ assert rel.rId == "rId9"
+ assert rel.reltype == "ReLtYpE"
+ assert rel.target_ref == "foo/bar.xml"
+ assert rel.targetMode == RTM.INTERNAL
+ assert rel.xml == (
+ f''
+ )
+
+ def and_it_constructs_an_external_relationship_when_target_mode_EXTERNAL_is_specified(self):
+ rel = CT_Relationship.new("rId9", "ReLtYpE", "http://some/link", RTM.EXTERNAL)
+
+ assert rel.rId == "rId9"
+ assert rel.reltype == "ReLtYpE"
+ assert rel.target_ref == "http://some/link"
+ assert rel.targetMode == RTM.EXTERNAL
+ assert rel.xml == (
+ f''
+ )
+
+
+class DescribeCT_Relationships:
"""Unit-test suite for `pptx.opc.oxml.CT_Relationships` objects."""
def it_can_construct_a_new_relationships_element(self):
rels = CT_Relationships.new()
- expected_xml = (
- "\n"
- ''
+ assert rels.xml == (
+ ''
)
- assert rels.xml.decode("utf-8") == expected_xml
def it_can_build_rels_element_incrementally(self):
- # setup ------------------------
rels = CT_Relationships.new()
- # exercise ---------------------
+
rels.add_rel("rId1", "http://reltype1", "docProps/core.xml")
rels.add_rel("rId2", "http://linktype", "http://some/link", True)
rels.add_rel("rId3", "http://reltype2", "../slides/slide1.xml")
- # verify -----------------------
- expected_rels_xml = a_Relationships().xml
- actual_xml = oxml_tostring(rels, encoding="unicode", pretty_print=True)
- assert actual_xml == expected_rels_xml
+
+ assert oxml_tostring(rels, encoding="unicode", pretty_print=True) == (
+ '\n'
+ ' \n'
+ ' \n'
+ ' \n'
+ "\n"
+ )
def it_can_generate_rels_file_xml(self):
- expected_xml = (
+ assert CT_Relationships.new().xml_file_bytes == (
"\n"
''.encode("utf-8")
)
- assert CT_Relationships.new().xml == expected_xml
-class DescribeCT_Types(object):
+class DescribeCT_Types:
"""Unit-test suite for `pptx.opc.oxml.CT_Types` objects."""
- def it_provides_access_to_default_child_elements(self):
- types = a_Types().element
+ def it_provides_access_to_default_child_elements(self, types: CT_Types):
assert len(types.default_lst) == 2
for default in types.default_lst:
assert isinstance(default, CT_Default)
- def it_provides_access_to_override_child_elements(self):
- types = a_Types().element
+ def it_provides_access_to_override_child_elements(self, types: CT_Types):
assert len(types.override_lst) == 3
for override in types.override_lst:
assert isinstance(override, CT_Override)
def it_should_have_empty_list_on_no_matching_elements(self):
- types = a_Types().empty().element
+ types = cast(CT_Types, element("ct:Types"))
assert types.default_lst == []
assert types.override_lst == []
def it_can_construct_a_new_types_element(self):
types = CT_Types.new()
- expected_xml = a_Types().empty().xml
- assert types.xml == expected_xml
+ assert types.xml == (
+ '\n'
+ )
def it_can_build_types_element_incrementally(self):
types = CT_Types.new()
types.add_default("xml", "application/xml")
types.add_default("jpeg", "image/jpeg")
- types.add_override("/docProps/core.xml", "app/vnd.type1")
- types.add_override("/ppt/presentation.xml", "app/vnd.type2")
- types.add_override("/docProps/thumbnail.jpeg", "image/jpeg")
- expected_types_xml = a_Types().xml
- assert types.xml == expected_types_xml
+ types.add_override(PackURI("/docProps/core.xml"), "app/vnd.type1")
+ types.add_override(PackURI("/ppt/presentation.xml"), "app/vnd.type2")
+ types.add_override(PackURI("/docProps/thumbnail.jpeg"), "image/jpeg")
+ assert types.xml == (
+ '\n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' \n'
+ "\n"
+ )
+
+ # -- fixtures ----------------------------------------------------
+
+ @pytest.fixture
+ def types(self) -> CT_Types:
+ return cast(
+ CT_Types,
+ element(
+ "ct:Types/(ct:Default{Extension=xml,ContentType=application/xml}"
+ ",ct:Default{Extension=jpeg,ContentType=image/jpeg}"
+ ",ct:Override{PartName=/docProps/core.xml,ContentType=app/vnd.type1}"
+ ",ct:Override{PartName=/ppt/presentation.xml,ContentType=app/vnd.type2}"
+ ",ct:Override{PartName=/docProps/thunbnail.jpeg,ContentType=image/jpeg})"
+ ),
+ )
-class Describe_serialize_part_xml(object):
+class Describe_serialize_part_xml:
"""Unit-test suite for `pptx.opc.oxml.serialize_part_xml` function."""
- def it_produces_properly_formatted_xml_for_an_opc_part(
- self, part_elm, expected_part_xml
- ):
+ def it_produces_properly_formatted_xml_for_an_opc_part(self):
"""
Tested aspects:
---------------
@@ -156,27 +194,18 @@ def it_produces_properly_formatted_xml_for_an_opc_part(
* [X] it preserves unused namespaces
* [X] it returns bytes ready to save to file (not unicode)
"""
+ part_elm = cast(
+ BaseOxmlElement,
+ parse_xml(
+ '\n fØØ'
+ "bÅr\n\n"
+ ),
+ )
xml = serialize_part_xml(part_elm)
- assert xml == expected_part_xml
# xml contains 134 chars, of which 3 are double-byte; it will have
# len of 134 if it's unicode and 137 if it's bytes
assert len(xml) == 137
-
- # fixtures -----------------------------------
-
- @pytest.fixture
- def part_elm(self):
- return parse_xml(
- '\n fØØ'
- "bÅr\n\n"
- )
-
- @pytest.fixture
- def expected_part_xml(self):
- unicode_xml = (
+ assert xml == (
"\n"
- 'fØØbÅr<'
- "/f:bar>"
- )
- xml_bytes = unicode_xml.encode("utf-8")
- return xml_bytes
+ 'fØØbÅr'
+ ).encode("utf-8")
diff --git a/tests/opc/test_package.py b/tests/opc/test_package.py
index d8bf20703..8c0e95809 100644
--- a/tests/opc/test_package.py
+++ b/tests/opc/test_package.py
@@ -1,18 +1,19 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.opc.package` module."""
+from __future__ import annotations
+
import collections
import io
import itertools
+from typing import Any
import pytest
-from pptx.opc.constants import (
- CONTENT_TYPE as CT,
- RELATIONSHIP_TARGET_MODE as RTM,
- RELATIONSHIP_TYPE as RT,
-)
+from pptx.opc.constants import CONTENT_TYPE as CT
+from pptx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM
+from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.opc.oxml import CT_Relationship, CT_Relationships
from pptx.opc.package import (
OpcPackage,
@@ -30,9 +31,11 @@
from pptx.parts.presentation import PresentationPart
from ..unitutil.cxml import element
-from ..unitutil.file import absjoin, snippet_bytes, testfile_bytes, test_file_dir
+from ..unitutil.file import absjoin, snippet_bytes, test_file_dir, testfile_bytes
from ..unitutil.mock import (
ANY,
+ FixtureRequest,
+ Mock,
call,
class_mock,
function_mock,
@@ -43,7 +46,7 @@
)
-class Describe_RelatableMixin(object):
+class Describe_RelatableMixin:
"""Unit-test suite for `pptx.opc.package._RelatableMixin`.
This mixin is used for both OpcPackage and Part because both a package and a part
@@ -60,9 +63,7 @@ def it_can_find_a_part_related_by_reltype(self, _rels_prop_, relationships_, par
relationships_.part_with_reltype.assert_called_once_with(RT.CHART)
assert related_part is part_
- def it_can_establish_a_relationship_to_another_part(
- self, _rels_prop_, relationships_, part_
- ):
+ def it_can_establish_a_relationship_to_another_part(self, _rels_prop_, relationships_, part_):
relationships_.get_or_add.return_value = "rId42"
_rels_prop_.return_value = relationships_
mixin = _RelatableMixin()
@@ -81,9 +82,7 @@ def and_it_can_establish_a_relationship_to_an_external_link(
rId = mixin.relate_to("http://url", RT.HYPERLINK, is_external=True)
- relationships_.get_or_add_ext_rel.assert_called_once_with(
- RT.HYPERLINK, "http://url"
- )
+ relationships_.get_or_add_ext_rel.assert_called_once_with(RT.HYPERLINK, "http://url")
assert rId == "rId24"
def it_can_find_a_related_part_by_rId(
@@ -131,7 +130,7 @@ def _rels_prop_(self, request):
return property_mock(request, _RelatableMixin, "_rels")
-class DescribeOpcPackage(object):
+class DescribeOpcPackage:
"""Unit-test suite for `pptx.opc.package.OpcPackage` objects."""
def it_can_open_a_pkg_file(self, request):
@@ -153,13 +152,9 @@ def it_can_drop_a_relationship(self, _rels_prop_, relationships_):
relationships_.pop.assert_called_once_with("rId42")
def it_can_iterate_over_its_parts(self, request):
- part_, part_2_ = [
- instance_mock(request, Part, name="part_%d" % i) for i in range(2)
- ]
+ part_, part_2_ = [instance_mock(request, Part, name="part_%d" % i) for i in range(2)]
rels_iter = (
- instance_mock(
- request, _Relationship, is_external=is_external, target_part=target
- )
+ instance_mock(request, _Relationship, is_external=is_external, target_part=target)
for is_external, target in (
(True, "http://some/url/"),
(False, part_),
@@ -187,9 +182,7 @@ def it_can_iterate_over_its_relationships(self, request, _rels_prop_):
+--------> | part_1 |
+--------+
"""
- part_0_, part_1_ = [
- instance_mock(request, Part, name="part_%d" % i) for i in range(2)
- ]
+ part_0_, part_1_ = [instance_mock(request, Part, name="part_%d" % i) for i in range(2)]
all_rels = tuple(
instance_mock(
request,
@@ -299,7 +292,7 @@ def _rels_prop_(self, request):
return property_mock(request, OpcPackage, "_rels")
-class Describe_PackageLoader(object):
+class Describe_PackageLoader:
"""Unit-test suite for `pptx.opc.package._PackageLoader` objects."""
def it_provides_a_load_interface_classmethod(self, request, package_):
@@ -328,10 +321,7 @@ def it_loads_the_package_to_help(self, request, _xml_rels_prop_):
rels_ = dict(
itertools.chain(
(("/", instance_mock(request, _Relationships)),),
- (
- ("partname_%d" % n, instance_mock(request, _Relationships))
- for n in range(1, 4)
- ),
+ (("partname_%d" % n, instance_mock(request, _Relationships)) for n in range(1, 4)),
)
)
_xml_rels_prop_.return_value = rels_
@@ -340,9 +330,7 @@ def it_loads_the_package_to_help(self, request, _xml_rels_prop_):
pkg_xml_rels, parts = package_loader._load()
for part_ in parts_.values():
- part_.load_rels_from_xml.assert_called_once_with(
- rels_[part_.partname], parts_
- )
+ part_.load_rels_from_xml.assert_called_once_with(rels_[part_.partname], parts_)
assert pkg_xml_rels is rels_["/"]
assert parts is parts_
@@ -397,7 +385,7 @@ def _xml_rels_prop_(self, request):
return property_mock(request, _PackageLoader, "_xml_rels")
-class DescribePart(object):
+class DescribePart:
"""Unit-test suite for `pptx.opc.package.Part` objects."""
def it_can_be_constructed_by_PartFactory(self, request, package_):
@@ -420,19 +408,6 @@ def it_can_change_its_blob(self):
def it_knows_its_content_type(self):
assert Part(None, CT.PML_SLIDE, None).content_type == CT.PML_SLIDE
- @pytest.mark.parametrize("ref_count, calls", ((2, []), (1, [call("rId42")])))
- def it_can_drop_a_relationship(self, request, relationships_, ref_count, calls):
- _rel_ref_count_ = method_mock(
- request, Part, "_rel_ref_count", return_value=ref_count
- )
- property_mock(request, Part, "_rels", return_value=relationships_)
- part = Part(None, None, None)
-
- part.drop_rel("rId42")
-
- _rel_ref_count_.assert_called_once_with(part, "rId42")
- assert relationships_.pop.call_args_list == calls
-
def it_knows_the_package_it_belongs_to(self, package_):
assert Part(None, None, package_).package is package_
@@ -444,9 +419,7 @@ def it_can_change_its_partname(self):
part.partname = PackURI("/new/part/name")
assert part.partname == PackURI("/new/part/name")
- def it_provides_access_to_its_relationships_for_traversal(
- self, request, relationships_
- ):
+ def it_provides_access_to_its_relationships_for_traversal(self, request, relationships_):
property_mock(request, Part, "_rels", return_value=relationships_)
assert Part(None, None, None).rels is relationships_
@@ -484,16 +457,14 @@ def relationships_(self, request):
return instance_mock(request, _Relationships)
-class DescribeXmlPart(object):
+class DescribeXmlPart:
"""Unit-test suite for `pptx.opc.package.XmlPart` objects."""
def it_can_be_constructed_by_PartFactory(self, request):
partname = PackURI("/ppt/slides/slide1.xml")
element_ = element("p:sld")
package_ = instance_mock(request, OpcPackage)
- parse_xml_ = function_mock(
- request, "pptx.opc.package.parse_xml", return_value=element_
- )
+ parse_xml_ = function_mock(request, "pptx.opc.package.parse_xml", return_value=element_)
_init_ = initializer_mock(request, XmlPart)
part = XmlPart.load(partname, CT.PML_SLIDE, package_, b"blob")
@@ -504,9 +475,7 @@ def it_can_be_constructed_by_PartFactory(self, request):
def it_can_serialize_to_xml(self, request):
element_ = element("p:sld")
- serialize_part_xml_ = function_mock(
- request, "pptx.opc.package.serialize_part_xml"
- )
+ serialize_part_xml_ = function_mock(request, "pptx.opc.package.serialize_part_xml")
xml_part = XmlPart(None, None, None, element_)
blob = xml_part.blob
@@ -514,17 +483,34 @@ def it_can_serialize_to_xml(self, request):
serialize_part_xml_.assert_called_once_with(element_)
assert blob is serialize_part_xml_.return_value
+ @pytest.mark.parametrize(("ref_count", "calls"), [(2, []), (1, [call("rId42")])])
+ def it_can_drop_a_relationship(
+ self, request: FixtureRequest, relationships_: Mock, ref_count: int, calls: list[Any]
+ ):
+ _rel_ref_count_ = method_mock(request, XmlPart, "_rel_ref_count", return_value=ref_count)
+ property_mock(request, XmlPart, "_rels", return_value=relationships_)
+ part = XmlPart(None, None, None, None)
+
+ part.drop_rel("rId42")
+
+ _rel_ref_count_.assert_called_once_with(part, "rId42")
+ assert relationships_.pop.call_args_list == calls
+
def it_knows_it_is_the_part_for_its_child_objects(self):
xml_part = XmlPart(None, None, None, None)
assert xml_part.part is xml_part
+ # -- fixtures ----------------------------------------------------
+
+ @pytest.fixture
+ def relationships_(self, request):
+ return instance_mock(request, _Relationships)
+
-class DescribePartFactory(object):
+class DescribePartFactory:
"""Unit-test suite for `pptx.opc.package.PartFactory` objects."""
- def it_constructs_custom_part_type_for_registered_content_types(
- self, request, package_, part_
- ):
+ def it_constructs_custom_part_type_for_registered_content_types(self, request, package_, part_):
SlidePart_ = class_mock(request, "pptx.opc.package.XmlPart")
SlidePart_.load.return_value = part_
partname = PackURI("/ppt/slides/slide7.xml")
@@ -532,9 +518,7 @@ def it_constructs_custom_part_type_for_registered_content_types(
part = PartFactory(partname, CT.PML_SLIDE, package_, b"blob")
- SlidePart_.load.assert_called_once_with(
- partname, CT.PML_SLIDE, package_, b"blob"
- )
+ SlidePart_.load.assert_called_once_with(partname, CT.PML_SLIDE, package_, b"blob")
assert part is part_
def it_constructs_part_using_default_class_when_no_custom_registered(
@@ -546,9 +530,7 @@ def it_constructs_part_using_default_class_when_no_custom_registered(
part = PartFactory(partname, CT.OFC_VML_DRAWING, package_, b"blob")
- Part_.load.assert_called_once_with(
- partname, CT.OFC_VML_DRAWING, package_, b"blob"
- )
+ Part_.load.assert_called_once_with(partname, CT.OFC_VML_DRAWING, package_, b"blob")
assert part is part_
# fixtures components ----------------------------------
@@ -562,7 +544,7 @@ def part_(self, request):
return instance_mock(request, Part)
-class Describe_ContentTypeMap(object):
+class Describe_ContentTypeMap:
"""Unit-test suite for `pptx.opc.package._ContentTypeMap` objects."""
def it_can_construct_from_content_types_xml(self, request):
@@ -617,8 +599,7 @@ def it_raises_KeyError_on_partname_not_found(self, content_type_map):
with pytest.raises(KeyError) as e:
content_type_map[PackURI("/!blat/rhumba.1x&")]
assert str(e.value) == (
- "\"no content-type for partname '/!blat/rhumba.1x&' "
- 'in [Content_Types].xml"'
+ "\"no content-type for partname '/!blat/rhumba.1x&' " 'in [Content_Types].xml"'
)
def it_raises_TypeError_on_key_not_instance_of_PackURI(self, content_type_map):
@@ -630,12 +611,10 @@ def it_raises_TypeError_on_key_not_instance_of_PackURI(self, content_type_map):
@pytest.fixture(scope="class")
def content_type_map(self):
- return _ContentTypeMap.from_xml(
- testfile_bytes("expanded_pptx", "[Content_Types].xml")
- )
+ return _ContentTypeMap.from_xml(testfile_bytes("expanded_pptx", "[Content_Types].xml"))
-class Describe_Relationships(object):
+class Describe_Relationships:
"""Unit-test suite for `pptx.opc.package._Relationships` objects."""
@pytest.mark.parametrize("rId, expected_value", (("rId1", True), ("rId2", False)))
@@ -655,9 +634,7 @@ def but_it_raises_KeyError_when_no_relationship_has_rId(self, _rels_prop_):
_Relationships(None)["rId6"]
assert str(e.value) == "\"no relationship with key 'rId6'\""
- def it_can_iterate_the_rIds_of_the_relationships_it_contains(
- self, request, _rels_prop_
- ):
+ def it_can_iterate_the_rIds_of_the_relationships_it_contains(self, request, _rels_prop_):
rels_ = set(instance_mock(request, _Relationship) for n in range(5))
_rels_prop_.return_value = {"rId%d" % (i + 1): r for i, r in enumerate(rels_)}
relationships = _Relationships(None)
@@ -671,9 +648,7 @@ def it_has_a_len(self, _rels_prop_):
_rels_prop_.return_value = {"a": 0, "b": 1}
assert len(_Relationships(None)) == 2
- def it_can_add_a_relationship_to_a_target_part(
- self, part_, _get_matching_, _add_relationship_
- ):
+ def it_can_add_a_relationship_to_a_target_part(self, part_, _get_matching_, _add_relationship_):
_get_matching_.return_value = None
_add_relationship_.return_value = "rId7"
relationships = _Relationships(None)
@@ -684,9 +659,7 @@ def it_can_add_a_relationship_to_a_target_part(
_add_relationship_.assert_called_once_with(relationships, RT.IMAGE, part_)
assert rId == "rId7"
- def but_it_returns_an_existing_relationship_if_it_matches(
- self, part_, _get_matching_
- ):
+ def but_it_returns_an_existing_relationship_if_it_matches(self, part_, _get_matching_):
_get_matching_.return_value = "rId3"
relationships = _Relationships(None)
@@ -695,9 +668,7 @@ def but_it_returns_an_existing_relationship_if_it_matches(
_get_matching_.assert_called_once_with(relationships, RT.IMAGE, part_)
assert rId == "rId3"
- def it_can_add_an_external_relationship_to_a_URI(
- self, _get_matching_, _add_relationship_
- ):
+ def it_can_add_an_external_relationship_to_a_URI(self, _get_matching_, _add_relationship_):
_get_matching_.return_value = None
_add_relationship_.return_value = "rId2"
relationships = _Relationships(None)
@@ -712,9 +683,7 @@ def it_can_add_an_external_relationship_to_a_URI(
)
assert rId == "rId2"
- def but_it_returns_an_existing_external_relationship_if_it_matches(
- self, part_, _get_matching_
- ):
+ def but_it_returns_an_existing_external_relationship_if_it_matches(self, part_, _get_matching_):
_get_matching_.return_value = "rId10"
relationships = _Relationships(None)
@@ -727,8 +696,7 @@ def but_it_returns_an_existing_external_relationship_if_it_matches(
def it_can_load_from_the_xml_in_a_rels_part(self, request, _Relationship_, part_):
rels_ = tuple(
- instance_mock(request, _Relationship, rId="rId%d" % (i + 1))
- for i in range(5)
+ instance_mock(request, _Relationship, rId="rId%d" % (i + 1)) for i in range(5)
)
_Relationship_.from_xml.side_effect = iter(rels_)
parts = {"/ppt/slideLayouts/slideLayout1.xml": part_}
@@ -743,9 +711,7 @@ def it_can_load_from_the_xml_in_a_rels_part(self, request, _Relationship_, part_
]
assert relationships._rels == {"rId1": rels_[0], "rId2": rels_[1]}
- def it_can_find_a_part_with_reltype(
- self, _rels_by_reltype_prop_, relationship_, part_
- ):
+ def it_can_find_a_part_with_reltype(self, _rels_by_reltype_prop_, relationship_, part_):
relationship_.target_part = part_
_rels_by_reltype_prop_.return_value = collections.defaultdict(
list, ((RT.SLIDE_LAYOUT, [relationship_]),)
@@ -852,9 +818,7 @@ def and_it_can_add_an_external_relationship_to_help(
_rels_prop_.return_value = {}
relationships = _Relationships("/ppt")
- rId = relationships._add_relationship(
- RT.HYPERLINK, "http://url", is_external=True
- )
+ rId = relationships._add_relationship(RT.HYPERLINK, "http://url", is_external=True)
_Relationship_.assert_called_once_with(
"/ppt", "rId9", RT.HYPERLINK, target_mode=RTM.EXTERNAL, target="http://url"
@@ -894,18 +858,14 @@ def it_can_get_a_matching_relationship_to_help(
)
]
}
- target = (
- target_ref if is_external else part_1 if target_ref == "part_1" else part_2
- )
+ target = target_ref if is_external else part_1 if target_ref == "part_1" else part_2
relationships = _Relationships(None)
matching = relationships._get_matching(RT.SLIDE, target, is_external)
assert matching == expected_value
- def but_it_returns_None_when_there_is_no_matching_relationship(
- self, _rels_by_reltype_prop_
- ):
+ def but_it_returns_None_when_there_is_no_matching_relationship(self, _rels_by_reltype_prop_):
_rels_by_reltype_prop_.return_value = collections.defaultdict(list)
relationships = _Relationships(None)
@@ -979,7 +939,7 @@ def _rels_prop_(self, request):
return property_mock(request, _Relationships, "_rels")
-class Describe_Relationship(object):
+class Describe_Relationship:
"""Unit-test suite for `pptx.opc.package._Relationship` objects."""
def it_can_construct_from_xml(self, request, part_):
@@ -996,9 +956,7 @@ def it_can_construct_from_xml(self, request, part_):
relationship = _Relationship.from_xml("/ppt", rel_elm, parts)
- _init_.assert_called_once_with(
- relationship, "/ppt", "rId42", RT.SLIDE, RTM.INTERNAL, part_
- )
+ _init_.assert_called_once_with(relationship, "/ppt", "rId42", RT.SLIDE, RTM.INTERNAL, part_)
assert isinstance(relationship, _Relationship)
@pytest.mark.parametrize(
@@ -1026,8 +984,7 @@ def but_it_raises_ValueError_on_target_part_for_external_rel(self):
with pytest.raises(ValueError) as e:
relationship.target_part
assert str(e.value) == (
- "`.target_part` property on _Relationship is undefined when "
- "target-mode is external"
+ "`.target_part` property on _Relationship is undefined when " "target-mode is external"
)
def it_knows_its_target_partname(self, part_):
diff --git a/tests/opc/test_packuri.py b/tests/opc/test_packuri.py
index f77ea68f5..5b7e64a2f 100644
--- a/tests/opc/test_packuri.py
+++ b/tests/opc/test_packuri.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Unit-test suite for the `pptx.opc.packuri` module."""
+from __future__ import annotations
+
import pytest
from pptx.opc.packuri import PackURI
@@ -11,9 +11,7 @@ class DescribePackURI(object):
"""Unit-test suite for the `pptx.opc.packuri.PackURI` objects."""
def it_can_construct_from_relative_ref(self):
- pack_uri = PackURI.from_rel_ref(
- "/ppt/slides", "../slideLayouts/slideLayout1.xml"
- )
+ pack_uri = PackURI.from_rel_ref("/ppt/slides", "../slideLayouts/slideLayout1.xml")
assert pack_uri == "/ppt/slideLayouts/slideLayout1.xml"
def it_should_raise_on_construct_with_bad_pack_uri_str(self):
@@ -21,53 +19,53 @@ def it_should_raise_on_construct_with_bad_pack_uri_str(self):
PackURI("foobar")
@pytest.mark.parametrize(
- "uri, expected_value",
- (
+ ("uri", "expected_value"),
+ [
("/", "/"),
("/ppt/presentation.xml", "/ppt"),
("/ppt/slides/slide1.xml", "/ppt/slides"),
- ),
+ ],
)
- def it_knows_its_base_URI(self, uri, expected_value):
+ def it_knows_its_base_URI(self, uri: str, expected_value: str):
assert PackURI(uri).baseURI == expected_value
@pytest.mark.parametrize(
- "uri, expected_value",
- (
+ ("uri", "expected_value"),
+ [
("/", ""),
("/ppt/presentation.xml", "xml"),
("/ppt/media/image.PnG", "PnG"),
- ),
+ ],
)
- def it_knows_its_extension(self, uri, expected_value):
+ def it_knows_its_extension(self, uri: str, expected_value: str):
assert PackURI(uri).ext == expected_value
@pytest.mark.parametrize(
- "uri, expected_value",
- (
+ ("uri", "expected_value"),
+ [
("/", ""),
("/ppt/presentation.xml", "presentation.xml"),
("/ppt/media/image.png", "image.png"),
- ),
+ ],
)
- def it_knows_its_filename(self, uri, expected_value):
+ def it_knows_its_filename(self, uri: str, expected_value: str):
assert PackURI(uri).filename == expected_value
@pytest.mark.parametrize(
- "uri, expected_value",
- (
+ ("uri", "expected_value"),
+ [
("/", None),
("/ppt/presentation.xml", None),
("/ppt/,foo,grob!.xml", None),
("/ppt/media/image42.png", 42),
- ),
+ ],
)
- def it_knows_the_filename_index(self, uri, expected_value):
+ def it_knows_the_filename_index(self, uri: str, expected_value: str):
assert PackURI(uri).idx == expected_value
@pytest.mark.parametrize(
- "uri, base_uri, expected_value",
- (
+ ("uri", "base_uri", "expected_value"),
+ [
("/ppt/presentation.xml", "/", "ppt/presentation.xml"),
(
"/ppt/slideMasters/slideMaster1.xml",
@@ -79,18 +77,18 @@ def it_knows_the_filename_index(self, uri, expected_value):
"/ppt/slides",
"../slideLayouts/slideLayout1.xml",
),
- ),
+ ],
)
- def it_can_compute_its_relative_reference(self, uri, base_uri, expected_value):
+ def it_can_compute_its_relative_reference(self, uri: str, base_uri: str, expected_value: str):
assert PackURI(uri).relative_ref(base_uri) == expected_value
@pytest.mark.parametrize(
- "uri, expected_value",
- (
+ ("uri", "expected_value"),
+ [
("/", "/_rels/.rels"),
("/ppt/presentation.xml", "/ppt/_rels/presentation.xml.rels"),
("/ppt/slides/slide42.xml", "/ppt/slides/_rels/slide42.xml.rels"),
- ),
+ ],
)
- def it_knows_the_uri_of_its_rels_part(self, uri, expected_value):
+ def it_knows_the_uri_of_its_rels_part(self, uri: str, expected_value: str):
assert PackURI(uri).rels_uri == expected_value
diff --git a/tests/opc/test_serialized.py b/tests/opc/test_serialized.py
index 31c965904..d5b867c4e 100644
--- a/tests/opc/test_serialized.py
+++ b/tests/opc/test_serialized.py
@@ -1,13 +1,16 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.opc.serialized` module."""
+from __future__ import annotations
+
import hashlib
-import pytest
+import io
import zipfile
-from pptx.compat import BytesIO
-from pptx.exceptions import PackageNotFoundError
+import pytest
+
+from pptx.exc import PackageNotFoundError
from pptx.opc.constants import CONTENT_TYPE as CT
from pptx.opc.package import Part, _Relationships
from pptx.opc.packuri import CONTENT_TYPES_URI, PackURI
@@ -25,6 +28,8 @@
from ..unitutil.file import absjoin, snippet_text, test_file_dir
from ..unitutil.mock import (
ANY,
+ FixtureRequest,
+ Mock,
call,
class_mock,
function_mock,
@@ -34,41 +39,40 @@
property_mock,
)
-
test_pptx_path = absjoin(test_file_dir, "test.pptx")
dir_pkg_path = absjoin(test_file_dir, "expanded_pptx")
zip_pkg_path = test_pptx_path
-class DescribePackageReader(object):
+class DescribePackageReader:
"""Unit-test suite for `pptx.opc.serialized.PackageReader` objects."""
- def it_knows_whether_it_contains_a_partname(self, _blob_reader_prop_):
- _blob_reader_prop_.return_value = set(("/ppt", "/docProps"))
- package_reader = PackageReader(None)
+ def it_knows_whether_it_contains_a_partname(self, _blob_reader_prop_: Mock):
+ _blob_reader_prop_.return_value = {"/ppt", "/docProps"}
+ package_reader = PackageReader("")
assert "/ppt" in package_reader
assert "/xyz" not in package_reader
- def it_can_get_a_blob_by_partname(self, _blob_reader_prop_):
+ def it_can_get_a_blob_by_partname(self, _blob_reader_prop_: Mock):
_blob_reader_prop_.return_value = {"/ppt/slides/slide1.xml": b"blob"}
- package_reader = PackageReader(None)
+ package_reader = PackageReader("")
- assert package_reader["/ppt/slides/slide1.xml"] == b"blob"
+ assert package_reader[PackURI("/ppt/slides/slide1.xml")] == b"blob"
- def it_can_get_the_rels_xml_for_a_partname(self, _blob_reader_prop_):
+ def it_can_get_the_rels_xml_for_a_partname(self, _blob_reader_prop_: Mock):
_blob_reader_prop_.return_value = {"/ppt/_rels/presentation.xml.rels": b"blob"}
- package_reader = PackageReader(None)
+ package_reader = PackageReader("")
assert package_reader.rels_xml_for(PackURI("/ppt/presentation.xml")) == b"blob"
- def but_it_returns_None_when_the_part_has_no_rels(self, _blob_reader_prop_):
+ def but_it_returns_None_when_the_part_has_no_rels(self, _blob_reader_prop_: Mock):
_blob_reader_prop_.return_value = {"/ppt/_rels/presentation.xml.rels": b"blob"}
- package_reader = PackageReader(None)
+ package_reader = PackageReader("")
assert package_reader.rels_xml_for(PackURI("/ppt/slides.slide1.xml")) is None
- def it_constructs_its_blob_reader_to_help(self, request):
+ def it_constructs_its_blob_reader_to_help(self, request: FixtureRequest):
phys_pkg_reader_ = instance_mock(request, _PhysPkgReader)
_PhysPkgReader_ = class_mock(request, "pptx.opc.serialized._PhysPkgReader")
_PhysPkgReader_.factory.return_value = phys_pkg_reader_
@@ -82,25 +86,27 @@ def it_constructs_its_blob_reader_to_help(self, request):
# fixture components -----------------------------------
@pytest.fixture
- def _blob_reader_prop_(self, request):
+ def _blob_reader_prop_(self, request: FixtureRequest):
return property_mock(request, PackageReader, "_blob_reader")
-class DescribePackageWriter(object):
+class DescribePackageWriter:
"""Unit-test suite for `pptx.opc.serialized.PackageWriter` objects."""
- def it_provides_a_write_interface_classmethod(self, request, relationships_):
+ def it_provides_a_write_interface_classmethod(
+ self, request: FixtureRequest, relationships_: Mock, part_: Mock
+ ):
_init_ = initializer_mock(request, PackageWriter)
_write_ = method_mock(request, PackageWriter, "_write")
- PackageWriter.write("prs.pptx", relationships_, ("part_1", "part_2"))
+ PackageWriter.write("prs.pptx", relationships_, (part_, part_))
- _init_.assert_called_once_with(
- ANY, "prs.pptx", relationships_, ("part_1", "part_2")
- )
+ _init_.assert_called_once_with(ANY, "prs.pptx", relationships_, (part_, part_))
_write_.assert_called_once_with(ANY)
- def it_can_write_a_package(self, request, phys_writer_):
+ def it_can_write_a_package(
+ self, request: FixtureRequest, phys_writer_: Mock, relationships_: Mock
+ ):
_PhysPkgWriter_ = class_mock(request, "pptx.opc.serialized._PhysPkgWriter")
phys_writer_.__enter__.return_value = phys_writer_
_PhysPkgWriter_.factory.return_value = phys_writer_
@@ -109,35 +115,35 @@ def it_can_write_a_package(self, request, phys_writer_):
)
_write_pkg_rels_ = method_mock(request, PackageWriter, "_write_pkg_rels")
_write_parts_ = method_mock(request, PackageWriter, "_write_parts")
- package_writer = PackageWriter("prs.pptx", None, None)
+ package_writer = PackageWriter("prs.pptx", relationships_, [])
package_writer._write()
_PhysPkgWriter_.factory.assert_called_once_with("prs.pptx")
- _write_content_types_stream_.assert_called_once_with(
- package_writer, phys_writer_
- )
+ _write_content_types_stream_.assert_called_once_with(package_writer, phys_writer_)
_write_pkg_rels_.assert_called_once_with(package_writer, phys_writer_)
_write_parts_.assert_called_once_with(package_writer, phys_writer_)
- def it_can_write_a_content_types_stream(self, request, phys_writer_):
- _ContentTypesItem_ = class_mock(
- request, "pptx.opc.serialized._ContentTypesItem"
- )
+ def it_can_write_a_content_types_stream(
+ self, request: FixtureRequest, phys_writer_: Mock, relationships_: Mock, part_: Mock
+ ):
+ _ContentTypesItem_ = class_mock(request, "pptx.opc.serialized._ContentTypesItem")
_ContentTypesItem_.xml_for.return_value = "part_xml"
serialize_part_xml_ = function_mock(
request, "pptx.opc.serialized.serialize_part_xml", return_value=b"xml"
)
- package_writer = PackageWriter(None, None, ("part_1", "part_2"))
+ package_writer = PackageWriter("", relationships_, (part_, part_))
package_writer._write_content_types_stream(phys_writer_)
- _ContentTypesItem_.xml_for.assert_called_once_with(("part_1", "part_2"))
+ _ContentTypesItem_.xml_for.assert_called_once_with((part_, part_))
serialize_part_xml_.assert_called_once_with("part_xml")
phys_writer_.write.assert_called_once_with(CONTENT_TYPES_URI, b"xml")
- def it_can_write_a_sequence_of_parts(self, request, phys_writer_):
- parts_ = (
+ def it_can_write_a_sequence_of_parts(
+ self, request: FixtureRequest, relationships_: Mock, phys_writer_: Mock
+ ):
+ parts_ = [
instance_mock(
request,
Part,
@@ -146,8 +152,8 @@ def it_can_write_a_sequence_of_parts(self, request, phys_writer_):
rels=instance_mock(request, _Relationships, xml="rels_xml_%s" % x),
)
for x in ("a", "b", "c")
- )
- package_writer = PackageWriter(None, None, parts_)
+ ]
+ package_writer = PackageWriter("", relationships_, parts_)
package_writer._write_parts(phys_writer_)
@@ -160,40 +166,44 @@ def it_can_write_a_sequence_of_parts(self, request, phys_writer_):
call("/ppt/_rels/c.xml.rels", "rels_xml_c"),
]
- def it_can_write_a_pkg_rels_item(self, request, phys_writer_, relationships_):
+ def it_can_write_a_pkg_rels_item(self, phys_writer_: Mock, relationships_: Mock):
relationships_.xml = b"pkg-rels-xml"
- package_writer = PackageWriter(None, relationships_, None)
+ package_writer = PackageWriter("", relationships_, [])
package_writer._write_pkg_rels(phys_writer_)
phys_writer_.write.assert_called_once_with("/_rels/.rels", b"pkg-rels-xml")
- # fixture components -----------------------------------
+ # -- fixtures ----------------------------------------------------
+
+ @pytest.fixture
+ def part_(self, request: FixtureRequest):
+ return instance_mock(request, Part)
@pytest.fixture
- def phys_writer_(self, request):
+ def phys_writer_(self, request: FixtureRequest):
return instance_mock(request, _ZipPkgWriter)
@pytest.fixture
- def relationships_(self, request):
+ def relationships_(self, request: FixtureRequest):
return instance_mock(request, _Relationships)
-class Describe_PhysPkgReader(object):
+class Describe_PhysPkgReader:
"""Unit-test suite for `pptx.opc.serialized._PhysPkgReader` objects."""
def it_constructs_ZipPkgReader_when_pkg_is_file_like(
- self, _ZipPkgReader_, zip_pkg_reader_
+ self, _ZipPkgReader_: Mock, zip_pkg_reader_: Mock
):
_ZipPkgReader_.return_value = zip_pkg_reader_
- file_like_pkg = BytesIO(b"pkg-bytes")
+ file_like_pkg = io.BytesIO(b"pkg-bytes")
phys_reader = _PhysPkgReader.factory(file_like_pkg)
_ZipPkgReader_.assert_called_once_with(file_like_pkg)
assert phys_reader is zip_pkg_reader_
- def and_it_constructs_DirPkgReader_when_pkg_is_a_dir(self, request):
+ def and_it_constructs_DirPkgReader_when_pkg_is_a_dir(self, request: FixtureRequest):
dir_pkg_reader_ = instance_mock(request, _DirPkgReader)
_DirPkgReader_ = class_mock(
request, "pptx.opc.serialized._DirPkgReader", return_value=dir_pkg_reader_
@@ -205,7 +215,7 @@ def and_it_constructs_DirPkgReader_when_pkg_is_a_dir(self, request):
assert phys_reader is dir_pkg_reader_
def and_it_constructs_ZipPkgReader_when_pkg_is_a_zip_file_path(
- self, _ZipPkgReader_, zip_pkg_reader_
+ self, _ZipPkgReader_: Mock, zip_pkg_reader_: Mock
):
_ZipPkgReader_.return_value = zip_pkg_reader_
pkg_file_path = test_pptx_path
@@ -223,29 +233,27 @@ def but_it_raises_when_pkg_path_is_not_a_package(self):
# --- fixture components -------------------------------
@pytest.fixture
- def zip_pkg_reader_(self, request):
+ def zip_pkg_reader_(self, request: FixtureRequest):
return instance_mock(request, _ZipPkgReader)
@pytest.fixture
- def _ZipPkgReader_(self, request):
+ def _ZipPkgReader_(self, request: FixtureRequest):
return class_mock(request, "pptx.opc.serialized._ZipPkgReader")
-class Describe_DirPkgReader(object):
+class Describe_DirPkgReader:
"""Unit-test suite for `pptx.opc.serialized._DirPkgReader` objects."""
- def it_knows_whether_it_contains_a_partname(self, dir_pkg_reader):
+ def it_knows_whether_it_contains_a_partname(self, dir_pkg_reader: _DirPkgReader):
assert PackURI("/ppt/presentation.xml") in dir_pkg_reader
assert PackURI("/ppt/foobar.xml") not in dir_pkg_reader
- def it_can_retrieve_the_blob_for_a_pack_uri(self, dir_pkg_reader):
+ def it_can_retrieve_the_blob_for_a_pack_uri(self, dir_pkg_reader: _DirPkgReader):
blob = dir_pkg_reader[PackURI("/ppt/presentation.xml")]
- assert (
- hashlib.sha1(blob).hexdigest() == "51b78f4dabc0af2419d4e044ab73028c4bef53aa"
- )
+ assert hashlib.sha1(blob).hexdigest() == "51b78f4dabc0af2419d4e044ab73028c4bef53aa"
def but_it_raises_KeyError_when_requested_member_is_not_present(
- self, dir_pkg_reader
+ self, dir_pkg_reader: _DirPkgReader
):
with pytest.raises(KeyError) as e:
dir_pkg_reader[PackURI("/ppt/foobar.xml")]
@@ -254,31 +262,29 @@ def but_it_raises_KeyError_when_requested_member_is_not_present(
# --- fixture components -------------------------------
@pytest.fixture(scope="class")
- def dir_pkg_reader(self, request):
+ def dir_pkg_reader(self):
return _DirPkgReader(dir_pkg_path)
-class Describe_ZipPkgReader(object):
+class Describe_ZipPkgReader:
"""Unit-test suite for `pptx.opc.serialized._ZipPkgReader` objects."""
- def it_knows_whether_it_contains_a_partname(self, zip_pkg_reader):
+ def it_knows_whether_it_contains_a_partname(self, zip_pkg_reader: _ZipPkgReader):
assert PackURI("/ppt/presentation.xml") in zip_pkg_reader
assert PackURI("/ppt/foobar.xml") not in zip_pkg_reader
- def it_can_get_a_blob_by_partname(self, zip_pkg_reader):
+ def it_can_get_a_blob_by_partname(self, zip_pkg_reader: _ZipPkgReader):
blob = zip_pkg_reader[PackURI("/ppt/presentation.xml")]
- assert hashlib.sha1(blob).hexdigest() == (
- "efa7bee0ac72464903a67a6744c1169035d52a54"
- )
+ assert hashlib.sha1(blob).hexdigest() == ("efa7bee0ac72464903a67a6744c1169035d52a54")
def but_it_raises_KeyError_when_requested_member_is_not_present(
- self, zip_pkg_reader
+ self, zip_pkg_reader: _ZipPkgReader
):
with pytest.raises(KeyError) as e:
zip_pkg_reader[PackURI("/ppt/foobar.xml")]
assert str(e.value) == "\"no member '/ppt/foobar.xml' in package\""
- def it_loads_the_package_blobs_on_first_access_to_help(self, zip_pkg_reader):
+ def it_loads_the_package_blobs_on_first_access_to_help(self, zip_pkg_reader: _ZipPkgReader):
blobs = zip_pkg_reader._blobs
assert len(blobs) == 38
assert "/ppt/presentation.xml" in blobs
@@ -287,14 +293,14 @@ def it_loads_the_package_blobs_on_first_access_to_help(self, zip_pkg_reader):
# --- fixture components -------------------------------
@pytest.fixture(scope="class")
- def zip_pkg_reader(self, request):
+ def zip_pkg_reader(self):
return _ZipPkgReader(zip_pkg_path)
-class Describe_PhysPkgWriter(object):
+class Describe_PhysPkgWriter:
"""Unit-test suite for `pptx.opc.serialized._PhysPkgWriter` objects."""
- def it_constructs_ZipPkgWriter_unconditionally(self, request):
+ def it_constructs_ZipPkgWriter_unconditionally(self, request: FixtureRequest):
zip_pkg_writer_ = instance_mock(request, _ZipPkgWriter)
_ZipPkgWriter_ = class_mock(
request, "pptx.opc.serialized._ZipPkgWriter", return_value=zip_pkg_writer_
@@ -306,22 +312,22 @@ def it_constructs_ZipPkgWriter_unconditionally(self, request):
assert phys_writer is zip_pkg_writer_
-class Describe_ZipPkgWriter(object):
+class Describe_ZipPkgWriter:
"""Unit-test suite for `pptx.opc.serialized._ZipPkgWriter` objects."""
def it_has_an__enter__method_for_context_management(self):
- pkg_writer = _ZipPkgWriter(None)
+ pkg_writer = _ZipPkgWriter("")
assert pkg_writer.__enter__() is pkg_writer
- def and_it_closes_the_zip_archive_on_context__exit__(self, _zipf_prop_):
- _ZipPkgWriter(None).__exit__(None, None, None)
+ def and_it_closes_the_zip_archive_on_context__exit__(self, _zipf_prop_: Mock):
+ _ZipPkgWriter("").__exit__()
_zipf_prop_.return_value.close.assert_called_once_with()
- def it_can_write_a_blob(self, _zipf_prop_):
+ def it_can_write_a_blob(self, _zipf_prop_: Mock):
"""Integrates with zipfile.ZipFile."""
pack_uri = PackURI("/part/name.xml")
- _zipf_prop_.return_value = zipf = zipfile.ZipFile(BytesIO(), "w")
- pkg_writer = _ZipPkgWriter(None)
+ _zipf_prop_.return_value = zipf = zipfile.ZipFile(io.BytesIO(), "w")
+ pkg_writer = _ZipPkgWriter("")
pkg_writer.write(pack_uri, b"blob")
@@ -329,37 +335,35 @@ def it_can_write_a_blob(self, _zipf_prop_):
assert len(members) == 1
assert members[pack_uri] == b"blob"
- def it_provides_access_to_the_open_zip_file_to_help(self, request):
+ def it_provides_access_to_the_open_zip_file_to_help(self, request: FixtureRequest):
ZipFile_ = class_mock(request, "pptx.opc.serialized.zipfile.ZipFile")
pkg_writer = _ZipPkgWriter("prs.pptx")
zipf = pkg_writer._zipf
- ZipFile_.assert_called_once_with(
- "prs.pptx", "w", compression=zipfile.ZIP_DEFLATED
- )
+ ZipFile_.assert_called_once_with("prs.pptx", "w", compression=zipfile.ZIP_DEFLATED)
assert zipf is ZipFile_.return_value
# fixtures ---------------------------------------------
@pytest.fixture
- def _zipf_prop_(self, request):
+ def _zipf_prop_(self, request: FixtureRequest):
return property_mock(request, _ZipPkgWriter, "_zipf")
-class Describe_ContentTypesItem(object):
+class Describe_ContentTypesItem:
"""Unit-test suite for `pptx.opc.serialized._ContentTypesItem` objects."""
- def it_provides_an_interface_classmethod(self, request):
+ def it_provides_an_interface_classmethod(self, request: FixtureRequest, part_: Mock):
_init_ = initializer_mock(request, _ContentTypesItem)
property_mock(request, _ContentTypesItem, "_xml", return_value=b"xml")
- xml = _ContentTypesItem.xml_for(("part", "zuh"))
+ xml = _ContentTypesItem.xml_for((part_, part_))
- _init_.assert_called_once_with(ANY, ("part", "zuh"))
+ _init_.assert_called_once_with(ANY, (part_, part_))
assert xml == b"xml"
- def it_can_compose_content_types_xml(self, request):
+ def it_can_compose_content_types_xml(self, request: FixtureRequest):
defaults = {"png": CT.PNG, "xml": CT.XML, "rels": CT.OPC_RELATIONSHIPS}
overrides = {
"/docProps/core.xml": "app/vnd.core",
@@ -373,22 +377,20 @@ def it_can_compose_content_types_xml(self, request):
return_value=(defaults, overrides),
)
- content_types = _ContentTypesItem(None)._xml
+ content_types = _ContentTypesItem([])._xml
assert content_types.xml == snippet_text("content-types-xml").strip()
- def it_computes_defaults_and_overrides_to_help(self, request):
- parts = (
- instance_mock(
- request, Part, partname=PackURI(partname), content_type=content_type
- )
+ def it_computes_defaults_and_overrides_to_help(self, request: FixtureRequest):
+ parts = [
+ instance_mock(request, Part, partname=PackURI(partname), content_type=content_type)
for partname, content_type in (
("/media/image1.png", CT.PNG),
("/ppt/slides/slide1.xml", CT.PML_SLIDE),
("/foo/bar.xml", CT.XML),
("/docProps/core.xml", CT.OPC_CORE_PROPERTIES),
)
- )
+ ]
content_types = _ContentTypesItem(parts)
defaults, overrides = content_types._defaults_and_overrides
@@ -398,3 +400,9 @@ def it_computes_defaults_and_overrides_to_help(self, request):
"/ppt/slides/slide1.xml": CT.PML_SLIDE,
"/docProps/core.xml": CT.OPC_CORE_PROPERTIES,
}
+
+ # -- fixtures ----------------------------------------------------
+
+ @pytest.fixture
+ def part_(self, request: FixtureRequest):
+ return instance_mock(request, Part)
diff --git a/tests/opc/unitdata/__init__.py b/tests/opc/unitdata/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/tests/opc/unitdata/rels.py b/tests/opc/unitdata/rels.py
deleted file mode 100644
index 2fd0aa947..000000000
--- a/tests/opc/unitdata/rels.py
+++ /dev/null
@@ -1,261 +0,0 @@
-# encoding: utf-8
-
-"""Test data for relationship-related unit tests."""
-
-from pptx.opc.constants import NAMESPACE as NS
-from pptx.oxml import parse_xml
-
-
-class BaseBuilder(object):
- """
- Provides common behavior for all data builders.
- """
-
- @property
- def element(self):
- """Return element based on XML generated by builder"""
- return parse_xml(self.xml)
-
- def with_indent(self, indent):
- """Add integer *indent* spaces at beginning of element XML"""
- self._indent = indent
- return self
-
-
-class CT_DefaultBuilder(BaseBuilder):
- """
- Test data builder for CT_Default (Default) XML element that appears in
- `[Content_Types].xml`.
- """
-
- def __init__(self):
- """Establish instance variables with default values"""
- self._content_type = "application/xml"
- self._extension = "xml"
- self._indent = 0
- self._namespace = ' xmlns="%s"' % NS.OPC_CONTENT_TYPES
-
- def with_content_type(self, content_type):
- """Set ContentType attribute to *content_type*"""
- self._content_type = content_type
- return self
-
- def with_extension(self, extension):
- """Set Extension attribute to *extension*"""
- self._extension = extension
- return self
-
- def without_namespace(self):
- """Don't include an 'xmlns=' attribute"""
- self._namespace = ""
- return self
-
- @property
- def xml(self):
- """Return Default element"""
- tmpl = '%s\n'
- indent = " " * self._indent
- return tmpl % (indent, self._namespace, self._extension, self._content_type)
-
-
-class CT_OverrideBuilder(BaseBuilder):
- """
- Test data builder for CT_Override (Override) XML element that appears in
- `[Content_Types].xml`.
- """
-
- def __init__(self):
- """Establish instance variables with default values"""
- self._content_type = "app/vnd.type"
- self._indent = 0
- self._namespace = ' xmlns="%s"' % NS.OPC_CONTENT_TYPES
- self._partname = "/part/name.xml"
-
- def with_content_type(self, content_type):
- """Set ContentType attribute to *content_type*"""
- self._content_type = content_type
- return self
-
- def with_partname(self, partname):
- """Set PartName attribute to *partname*"""
- self._partname = partname
- return self
-
- def without_namespace(self):
- """Don't include an 'xmlns=' attribute"""
- self._namespace = ""
- return self
-
- @property
- def xml(self):
- """Return Override element"""
- tmpl = '%s\n'
- indent = " " * self._indent
- return tmpl % (indent, self._namespace, self._partname, self._content_type)
-
-
-class CT_RelationshipBuilder(BaseBuilder):
- """
- Test data builder for CT_Relationship (Relationship) XML element that
- appears in .rels files
- """
-
- def __init__(self):
- """Establish instance variables with default values"""
- self._rId = "rId9"
- self._reltype = "ReLtYpE"
- self._target = "docProps/core.xml"
- self._target_mode = None
- self._indent = 0
- self._namespace = ' xmlns="%s"' % NS.OPC_RELATIONSHIPS
-
- def with_rId(self, rId):
- """Set Id attribute to *rId*"""
- self._rId = rId
- return self
-
- def with_reltype(self, reltype):
- """Set Type attribute to *reltype*"""
- self._reltype = reltype
- return self
-
- def with_target(self, target):
- """Set XXX attribute to *target*"""
- self._target = target
- return self
-
- def with_target_mode(self, target_mode):
- """Set TargetMode attribute to *target_mode*"""
- self._target_mode = None if target_mode == "Internal" else target_mode
- return self
-
- def without_namespace(self):
- """Don't include an 'xmlns=' attribute"""
- self._namespace = ""
- return self
-
- @property
- def target_mode(self):
- if self._target_mode is None:
- return ""
- return ' TargetMode="%s"' % self._target_mode
-
- @property
- def xml(self):
- """Return Relationship element"""
- tmpl = '%s\n'
- indent = " " * self._indent
- return tmpl % (
- indent,
- self._namespace,
- self._rId,
- self._reltype,
- self._target,
- self.target_mode,
- )
-
-
-class CT_RelationshipsBuilder(BaseBuilder):
- """
- Test data builder for CT_Relationships (Relationships) XML element, the
- root element in .rels files.
- """
-
- def __init__(self):
- """Establish instance variables with default values"""
- self._rels = (
- ("rId1", "http://reltype1", "docProps/core.xml", "Internal"),
- ("rId2", "http://linktype", "http://some/link", "External"),
- ("rId3", "http://reltype2", "../slides/slide1.xml", "Internal"),
- )
-
- @property
- def xml(self):
- """
- Return XML string based on settings accumulated via method calls.
- """
- xml = '\n' % NS.OPC_RELATIONSHIPS
- for rId, reltype, target, target_mode in self._rels:
- xml += (
- a_Relationship()
- .with_rId(rId)
- .with_reltype(reltype)
- .with_target(target)
- .with_target_mode(target_mode)
- .with_indent(2)
- .without_namespace()
- .xml
- )
- xml += "\n"
- return xml
-
-
-class CT_TypesBuilder(BaseBuilder):
- """
- Test data builder for CT_Types () XML element, the root element in
- [Content_Types].xml files
- """
-
- def __init__(self):
- """Establish instance variables with default values"""
- self._defaults = (("xml", "application/xml"), ("jpeg", "image/jpeg"))
- self._empty = False
- self._overrides = (
- ("/docProps/core.xml", "app/vnd.type1"),
- ("/ppt/presentation.xml", "app/vnd.type2"),
- ("/docProps/thumbnail.jpeg", "image/jpeg"),
- )
-
- def empty(self):
- self._empty = True
- return self
-
- @property
- def xml(self):
- """
- Return XML string based on settings accumulated via method calls
- """
- if self._empty:
- return '\n' % NS.OPC_CONTENT_TYPES
-
- xml = '\n' % NS.OPC_CONTENT_TYPES
- for extension, content_type in self._defaults:
- xml += (
- a_Default()
- .with_extension(extension)
- .with_content_type(content_type)
- .with_indent(2)
- .without_namespace()
- .xml
- )
- for partname, content_type in self._overrides:
- xml += (
- an_Override()
- .with_partname(partname)
- .with_content_type(content_type)
- .with_indent(2)
- .without_namespace()
- .xml
- )
- xml += "\n"
- return xml
-
-
-def a_Default():
- return CT_DefaultBuilder()
-
-
-def a_Relationship():
- return CT_RelationshipBuilder()
-
-
-def a_Relationships():
- return CT_RelationshipsBuilder()
-
-
-def a_Types():
- return CT_TypesBuilder()
-
-
-def an_Override():
- return CT_OverrideBuilder()
diff --git a/tests/oxml/shapes/test_autoshape.py b/tests/oxml/shapes/test_autoshape.py
index 020246d58..a03bc7f22 100644
--- a/tests/oxml/shapes/test_autoshape.py
+++ b/tests/oxml/shapes/test_autoshape.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for `pptx.oxml.autoshape` module."""
-"""
-Test suite for pptx.oxml.autoshape module.
-"""
-
-from __future__ import absolute_import, print_function
+from __future__ import annotations
import pytest
@@ -13,8 +9,8 @@
from pptx.oxml.shapes.autoshape import CT_Shape
from pptx.oxml.shapes.shared import ST_Direction, ST_PlaceholderSize
-from ..unitdata.shape import a_gd, a_prstGeom, an_avLst
from ...unitutil.cxml import element
+from ..unitdata.shape import a_gd, a_prstGeom, an_avLst
class DescribeCT_PresetGeometry2D(object):
@@ -77,9 +73,7 @@ def prstGeom_bldr(self, prst, gd_vals):
for name, fmla in gd_vals:
gd_bldr = a_gd().with_name(name).with_fmla(fmla)
avLst_bldr.with_child(gd_bldr)
- prstGeom_bldr = (
- a_prstGeom().with_nsdecls().with_prst(prst).with_child(avLst_bldr)
- )
+ prstGeom_bldr = a_prstGeom().with_nsdecls().with_prst(prst).with_child(avLst_bldr)
return prstGeom_bldr
@@ -103,8 +97,7 @@ def it_knows_how_to_create_a_new_autoshape_sp(self):
'schemeClr val="lt1"/>\n \n \n \n \n '
'\n \n \n \n \n\n"
- % (nsdecls("a", "p"), id_, name, left, top, width, height, prst)
+ ">\n\n" % (nsdecls("a", "p"), id_, name, left, top, width, height, prst)
)
# exercise ---------------------
sp = CT_Shape.new_autoshape_sp(id_, name, prst, left, top, width, height)
diff --git a/tests/oxml/shapes/test_graphfrm.py b/tests/oxml/shapes/test_graphfrm.py
index 887d95290..1f1124ec5 100644
--- a/tests/oxml/shapes/test_graphfrm.py
+++ b/tests/oxml/shapes/test_graphfrm.py
@@ -1,14 +1,13 @@
-# encoding: utf-8
-
"""Unit-test suite for pptx.oxml.graphfrm module."""
+from __future__ import annotations
+
import pytest
from pptx.oxml.shapes.graphfrm import CT_GraphicalObjectFrame
from ...unitutil.cxml import xml
-
CHART_URI = "http://schemas.openxmlformats.org/drawingml/2006/chart"
TABLE_URI = "http://schemas.openxmlformats.org/drawingml/2006/table"
@@ -23,9 +22,7 @@ def it_can_construct_a_new_graphicFrame(self, new_graphicFrame_fixture):
def it_can_construct_a_new_chart_graphicFrame(self, new_chart_graphicFrame_fixture):
id_, name, rId, x, y, cx, cy, expected_xml = new_chart_graphicFrame_fixture
- graphicFrame = CT_GraphicalObjectFrame.new_chart_graphicFrame(
- id_, name, rId, x, y, cx, cy
- )
+ graphicFrame = CT_GraphicalObjectFrame.new_chart_graphicFrame(id_, name, rId, x, y, cx, cy)
assert graphicFrame.xml == expected_xml
def it_can_construct_a_new_table_graphicFrame(self, new_table_graphicFrame_fixture):
diff --git a/tests/oxml/shapes/test_groupshape.py b/tests/oxml/shapes/test_groupshape.py
index 66025261f..6884b06cd 100644
--- a/tests/oxml/shapes/test_groupshape.py
+++ b/tests/oxml/shapes/test_groupshape.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.oxml.shapes.groupshape` module."""
+from __future__ import annotations
+
import pytest
from pptx.oxml.shapes.autoshape import CT_Shape
@@ -23,12 +23,8 @@ def it_can_add_a_graphicFrame_element_containing_a_table(self, add_table_fixt):
graphicFrame = spTree.add_table(id_, name, rows, cols, x, y, cx, cy)
- new_table_graphicFrame_.assert_called_once_with(
- id_, name, rows, cols, x, y, cx, cy
- )
- insert_element_before_.assert_called_once_with(
- spTree, graphicFrame_, "p:extLst"
- )
+ new_table_graphicFrame_.assert_called_once_with(id_, name, rows, cols, x, y, cx, cy)
+ insert_element_before_.assert_called_once_with(spTree, graphicFrame_, "p:extLst")
assert graphicFrame is graphicFrame_
def it_can_add_a_grpSp_element(self, add_grpSp_fixture):
@@ -55,9 +51,7 @@ def it_can_add_an_sp_element_for_a_placeholder(self, add_placeholder_fixt):
sp = spTree.add_placeholder(id_, name, ph_type, orient, sz, idx)
- CT_Shape_.new_placeholder_sp.assert_called_once_with(
- id_, name, ph_type, orient, sz, idx
- )
+ CT_Shape_.new_placeholder_sp.assert_called_once_with(id_, name, ph_type, orient, sz, idx)
insert_element_before_.assert_called_once_with(spTree, sp_, "p:extLst")
assert sp is sp_
@@ -67,9 +61,7 @@ def it_can_add_an_sp_element_for_an_autoshape(self, add_autoshape_fixt):
sp = spTree.add_autoshape(id_, name, prst, x, y, cx, cy)
- CT_Shape_.new_autoshape_sp.assert_called_once_with(
- id_, name, prst, x, y, cx, cy
- )
+ CT_Shape_.new_autoshape_sp.assert_called_once_with(id_, name, prst, x, y, cx, cy)
insert_element_before_.assert_called_once_with(spTree, sp_, "p:extLst")
assert sp is sp_
diff --git a/tests/oxml/shapes/test_picture.py b/tests/oxml/shapes/test_picture.py
index 9f16599ba..546d6b0fd 100644
--- a/tests/oxml/shapes/test_picture.py
+++ b/tests/oxml/shapes/test_picture.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.oxml.shapes.picture` module."""
+from __future__ import annotations
+
import pytest
from pptx.oxml.ns import nsdecls
diff --git a/tests/oxml/test___init__.py b/tests/oxml/test___init__.py
index 176d8ace4..d4d163d09 100644
--- a/tests/oxml/test___init__.py
+++ b/tests/oxml/test___init__.py
@@ -1,13 +1,8 @@
-# encoding: utf-8
+"""Test suite for pptx.oxml.__init__.py module, primarily XML parser-related."""
-"""
-Test suite for pptx.oxml.__init__.py module, primarily XML parser-related.
-"""
-
-from __future__ import print_function, unicode_literals
+from __future__ import annotations
import pytest
-
from lxml import etree
from pptx.oxml import oxml_parser, parse_xml, register_element_cls
diff --git a/tests/oxml/test_dml.py b/tests/oxml/test_dml.py
index 8befa16c2..cc205b701 100644
--- a/tests/oxml/test_dml.py
+++ b/tests/oxml/test_dml.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for `pptx.oxml.dml` module."""
-"""
-Test suite for pptx.oxml.dml module.
-"""
-
-from __future__ import absolute_import, print_function
+from __future__ import annotations
import pytest
diff --git a/tests/oxml/test_ns.py b/tests/oxml/test_ns.py
index d4c4cc65d..0c4896f76 100644
--- a/tests/oxml/test_ns.py
+++ b/tests/oxml/test_ns.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Test suite for pptx.oxml.ns.py module."""
-"""
-Test suite for pptx.oxml.ns.py module.
-"""
-
-from __future__ import print_function, unicode_literals
+from __future__ import annotations
import pytest
@@ -44,16 +40,12 @@ def it_formats_namespace_declarations_from_a_list_of_prefixes(self, nsdecls_str)
class DescribeNsuri(object):
- def it_finds_the_namespace_uri_corresponding_to_a_namespace_prefix(
- self, namespace_uri_a
- ):
+ def it_finds_the_namespace_uri_corresponding_to_a_namespace_prefix(self, namespace_uri_a):
assert nsuri("a") == namespace_uri_a
class DescribeQn(object):
- def it_calculates_the_clark_name_for_an_ns_prefixed_tag_string(
- self, nsptag_str, clark_name
- ):
+ def it_calculates_the_clark_name_for_an_ns_prefixed_tag_string(self, nsptag_str, clark_name):
assert qn(nsptag_str) == clark_name
diff --git a/tests/oxml/test_presentation.py b/tests/oxml/test_presentation.py
index fc09cb444..1607ab5cc 100644
--- a/tests/oxml/test_presentation.py
+++ b/tests/oxml/test_presentation.py
@@ -1,46 +1,40 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
-"""
-Test suite for pptx.oxml.presentation module
-"""
+"""Unit-test suite for `pptx.oxml.presentation` module."""
-from __future__ import absolute_import, print_function, unicode_literals
+from __future__ import annotations
+
+from typing import cast
import pytest
+from pptx.oxml.presentation import CT_SlideIdList
+
from ..unitutil.cxml import element, xml
class DescribeCT_SlideIdList(object):
- def it_can_add_a_sldId_element_as_a_child(self, add_fixture):
- sldIdLst, expected_xml = add_fixture
- sldIdLst.add_sldId("rId1")
- assert sldIdLst.xml == expected_xml
+ """Unit-test suite for `pptx.oxml.presentation.CT_SlideIdLst` objects."""
- def it_knows_the_next_available_slide_id(self, next_id_fixture):
- sldIdLst, expected_id = next_id_fixture
- assert sldIdLst._next_id == expected_id
+ def it_can_add_a_sldId_element_as_a_child(self):
+ sldIdLst = cast(CT_SlideIdList, element("p:sldIdLst/p:sldId{r:id=rId4,id=256}"))
- # fixtures -------------------------------------------------------
+ sldIdLst.add_sldId("rId1")
- @pytest.fixture
- def add_fixture(self):
- sldIdLst = element("p:sldIdLst/p:sldId{r:id=rId4,id=256}")
- expected_xml = xml(
+ assert sldIdLst.xml == xml(
"p:sldIdLst/(p:sldId{r:id=rId4,id=256},p:sldId{r:id=rId1,id=257})"
)
- return sldIdLst, expected_xml
- @pytest.fixture(
- params=[
+ @pytest.mark.parametrize(
+ ("sldIdLst_cxml", "expected_value"),
+ [
("p:sldIdLst", 256),
("p:sldIdLst/p:sldId{id=42}", 256),
("p:sldIdLst/p:sldId{id=256}", 257),
("p:sldIdLst/(p:sldId{id=256},p:sldId{id=712})", 713),
("p:sldIdLst/(p:sldId{id=280},p:sldId{id=257})", 281),
- ]
+ ],
)
- def next_id_fixture(self, request):
- sldIdLst_cxml, expected_value = request.param
- sldIdLst = element(sldIdLst_cxml)
- return sldIdLst, expected_value
+ def it_knows_the_next_available_slide_id(self, sldIdLst_cxml: str, expected_value: int):
+ sldIdLst = cast(CT_SlideIdList, element(sldIdLst_cxml))
+ assert sldIdLst._next_id == expected_value
diff --git a/tests/oxml/test_simpletypes.py b/tests/oxml/test_simpletypes.py
index e1a98b2ef..261edf550 100644
--- a/tests/oxml/test_simpletypes.py
+++ b/tests/oxml/test_simpletypes.py
@@ -1,14 +1,20 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.oxml.simpletypes` module.
-`simpletypes` contains simple type class definitions. A simple type in this context
-corresponds to an `` e.g. `ST_Foobar` definition in the XML schema and
-provides data validation and type conversion services for use by xmlchemy. A simple-type
-generally corresponds to an element attribute whereas a complex type corresponds to an
-XML element (which itself can have multiple attributes and have child elements).
+The `simpletypes` module contains classes that each define a scalar-type that appears as an XML
+attribute.
+
+The term "simple-type", as distinct from "complex-type", is an XML Schema distinction. An XML
+attribute value must be a single string, and corresponds to a scalar value, like `bool`, `int`, or
+`str`. Complex-types describe _elements_, which can have multiple attributes as well as child
+elements.
+
+A simple type corresponds to an `` definition in the XML schema e.g. `ST_Foobar`.
+The `BaseSimpleType` subclass provides data validation and type conversion services for use by
+`xmlchemy`.
"""
+from __future__ import annotations
+
import pytest
from pptx.oxml.simpletypes import (
@@ -19,13 +25,13 @@
ST_Percentage,
)
-from ..unitutil.mock import method_mock, instance_mock
+from ..unitutil.mock import instance_mock, method_mock
class DescribeBaseSimpleType(object):
"""Unit-test suite for `pptx.oxml.simpletypes.BaseSimpleType` objects."""
- def it_can_convert_attr_value_to_python_type(
+ def it_can_convert_an_XML_attribute_value_to_a_python_type(
self, str_value_, py_value_, convert_from_xml_
):
py_value = ST_SimpleType.from_xml(str_value_)
diff --git a/tests/oxml/test_slide.py b/tests/oxml/test_slide.py
index d1b48ebc4..63b321da7 100644
--- a/tests/oxml/test_slide.py
+++ b/tests/oxml/test_slide.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.oxml.slide` module."""
+from __future__ import annotations
+
from pptx.oxml.slide import CT_NotesMaster, CT_NotesSlide
from ..unitutil.file import snippet_text
diff --git a/tests/oxml/test_table.py b/tests/oxml/test_table.py
index 02ce4b302..c64196f9b 100644
--- a/tests/oxml/test_table.py
+++ b/tests/oxml/test_table.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
-
"""Unit-test suite for pptx.oxml.table module"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
@@ -36,8 +34,7 @@ def it_can_create_a_new_tbl_element_tree(self):
"dyPr/>\n \n \n "
"\n \n \n \n \n "
" \n \n \n "
- "\n \n \n \n\n"
- % nsdecls("a")
+ "\n \n \n \n\n" % nsdecls("a")
)
tbl = CT_Table.new_tbl(2, 3, 334, 445)
assert tbl.xml == expected_xml
@@ -154,8 +151,7 @@ def dimensions_fixture(self, request):
("a:tbl/(a:tr/a:tc,a:tr/a:tc)", [0, 1], []),
("a:tbl/(a:tr/(a:tc,a:tc),a:tr/(a:tc,a:tc))", [2, 1], [1, 3]),
(
- "a:tbl/(a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc"
- ",a:tc))",
+ "a:tbl/(a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc" ",a:tc))",
[0, 8],
[1, 2, 4, 5, 7, 8],
),
@@ -174,8 +170,7 @@ def except_left_fixture(self, request):
("a:tbl/(a:tr/a:tc,a:tr/a:tc)", [0, 1], [1]),
("a:tbl/(a:tr/(a:tc,a:tc),a:tr/(a:tc,a:tc))", [2, 1], [2, 3]),
(
- "a:tbl/(a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc"
- ",a:tc))",
+ "a:tbl/(a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc" ",a:tc))",
[0, 8],
[3, 4, 5, 6, 7, 8],
),
@@ -194,9 +189,7 @@ def in_same_table_fixture(self, request):
tbl = element("a:tbl/a:tr/(a:tc,a:tc)")
other_tbl = element("a:tbl/a:tr/(a:tc,a:tc)")
tc = tbl.xpath("//a:tc")[0]
- other_tc = (
- tbl.xpath("//a:tc")[1] if expected_value else other_tbl.xpath("//a:tc")[1]
- )
+ other_tc = tbl.xpath("//a:tc")[1] if expected_value else other_tbl.xpath("//a:tc")[1]
return tc, other_tc, expected_value
@pytest.fixture(
@@ -205,8 +198,7 @@ def in_same_table_fixture(self, request):
("a:tbl/(a:tr/a:tc,a:tr/a:tc)", (0, 1), (0, 1)),
("a:tbl/(a:tr/(a:tc,a:tc),a:tr/(a:tc,a:tc))", (2, 1), (0, 2)),
(
- "a:tbl/(a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc"
- ",a:tc))",
+ "a:tbl/(a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc" ",a:tc))",
(4, 8),
(4, 7),
),
@@ -225,8 +217,7 @@ def left_col_fixture(self, request):
('a:tbl/a:tr/(a:tc/a:txBody/a:p,a:tc/a:txBody/a:p/a:r/a:t"b")', "b"),
('a:tbl/a:tr/(a:tc/a:txBody/a:p/a:r/a:t"a",a:tc/a:txBody/a:p)', "a"),
(
- 'a:tbl/a:tr/(a:tc/a:txBody/a:p/a:r/a:t"a",a:tc/a:txBody/a:p/a:r/a:t'
- '"b")',
+ 'a:tbl/a:tr/(a:tc/a:txBody/a:p/a:r/a:t"a",a:tc/a:txBody/a:p/a:r/a:t' '"b")',
"a\nb",
),
(
@@ -250,8 +241,7 @@ def move_fixture(self, request):
("a:tbl/(a:tr/a:tc,a:tr/a:tc)", (0, 1), (0,)),
("a:tbl/(a:tr/(a:tc,a:tc),a:tr/(a:tc,a:tc))", (2, 1), (0, 1)),
(
- "a:tbl/(a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc"
- ",a:tc))",
+ "a:tbl/(a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc,a:tc),a:tr/(a:tc,a:tc" ",a:tc))",
(4, 8),
(4, 5),
),
diff --git a/tests/oxml/test_theme.py b/tests/oxml/test_theme.py
index 9bff00568..87d051726 100644
--- a/tests/oxml/test_theme.py
+++ b/tests/oxml/test_theme.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for `pptx.oxml.theme` module."""
-"""
-Test suite for pptx.oxml.theme module
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
diff --git a/tests/oxml/test_xmlchemy.py b/tests/oxml/test_xmlchemy.py
index 6fd88f831..abb38b7f8 100644
--- a/tests/oxml/test_xmlchemy.py
+++ b/tests/oxml/test_xmlchemy.py
@@ -1,12 +1,10 @@
-# encoding: utf-8
+"""Unit-test suite for the `pptx.oxml.xmlchemy` module.
-"""
-Test suite for the pptx.oxml.xmlchemy module, focused on the metaclass and
-element and attribute definition classes. A major part of the fixture is
-provided by the metaclass-built test classes at the end of the file.
+Focused on the metaclass and element and attribute definition classes. A major part of the fixture
+is provided by the metaclass-built test classes at the end of the file.
"""
-from __future__ import absolute_import, print_function
+from __future__ import annotations
import pytest
@@ -48,22 +46,16 @@ def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
parent, choice, expected_xml = insert_fixture
parent._insert_choice(choice)
assert parent.xml == expected_xml
- assert parent._insert_choice.__doc__.startswith(
- "Return the passed ```` "
- )
+ assert parent._insert_choice.__doc__.startswith("Return the passed ```` ")
def it_adds_an_add_method_for_the_child_element(self, add_fixture):
parent, expected_xml = add_fixture
choice = parent._add_choice()
assert parent.xml == expected_xml
assert isinstance(choice, CT_Choice)
- assert parent._add_choice.__doc__.startswith(
- "Add a new ```` child element "
- )
+ assert parent._add_choice.__doc__.startswith("Add a new ```` child element ")
- def it_adds_a_get_or_change_to_method_for_the_child_element(
- self, get_or_change_to_fixture
- ):
+ def it_adds_a_get_or_change_to_method_for_the_child_element(self, get_or_change_to_fixture):
parent, expected_xml = get_or_change_to_fixture
choice = parent.get_or_change_to_choice()
assert isinstance(choice, CT_Choice)
@@ -77,9 +69,7 @@ def add_fixture(self):
expected_xml = self.parent_bldr("choice").xml()
return parent, expected_xml
- @pytest.fixture(
- params=[("choice2", "choice"), (None, "choice"), ("choice", "choice")]
- )
+ @pytest.fixture(params=[("choice2", "choice"), (None, "choice"), ("choice", "choice")])
def get_or_change_to_fixture(self, request):
before_member_tag, after_member_tag = request.param
parent = self.parent_bldr(before_member_tag).element
@@ -96,10 +86,7 @@ def getter_fixture(self, request):
@pytest.fixture
def insert_fixture(self):
parent = (
- a_parent()
- .with_nsdecls()
- .with_child(an_oomChild())
- .with_child(an_oooChild())
+ a_parent().with_nsdecls().with_child(an_oomChild()).with_child(an_oooChild())
).element
choice = a_choice().with_nsdecls().element
expected_xml = (
@@ -156,27 +143,21 @@ def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
parent, oomChild, expected_xml = insert_fixture
parent._insert_oomChild(oomChild)
assert parent.xml == expected_xml
- assert parent._insert_oomChild.__doc__.startswith(
- "Return the passed ```` "
- )
+ assert parent._insert_oomChild.__doc__.startswith("Return the passed ```` ")
def it_adds_a_private_add_method_for_the_child_element(self, add_fixture):
parent, expected_xml = add_fixture
oomChild = parent._add_oomChild()
assert parent.xml == expected_xml
assert isinstance(oomChild, CT_OomChild)
- assert parent._add_oomChild.__doc__.startswith(
- "Add a new ```` child element "
- )
+ assert parent._add_oomChild.__doc__.startswith("Add a new ```` child element ")
def it_adds_a_public_add_method_for_the_child_element(self, add_fixture):
parent, expected_xml = add_fixture
oomChild = parent.add_oomChild()
assert parent.xml == expected_xml
assert isinstance(oomChild, CT_OomChild)
- assert parent._add_oomChild.__doc__.startswith(
- "Add a new ```` child element "
- )
+ assert parent._add_oomChild.__doc__.startswith("Add a new ```` child element ")
# fixtures -------------------------------------------------------
@@ -238,9 +219,7 @@ def it_adds_a_setter_property_for_the_attr(self, setter_fixture):
assert parent.xml == expected_xml
def it_adds_a_docstring_for_the_property(self):
- assert CT_Parent.optAttr.__doc__.startswith(
- "ST_IntegerType type-converted value of "
- )
+ assert CT_Parent.optAttr.__doc__.startswith("ST_IntegerType type-converted value of ")
# fixtures -------------------------------------------------------
@@ -271,9 +250,7 @@ def it_adds_a_setter_property_for_the_attr(self, setter_fixture):
assert parent.xml == expected_xml
def it_adds_a_docstring_for_the_property(self):
- assert CT_Parent.reqAttr.__doc__.startswith(
- "ST_IntegerType type-converted value of "
- )
+ assert CT_Parent.reqAttr.__doc__.startswith("ST_IntegerType type-converted value of ")
def it_raises_on_get_when_attribute_not_present(self):
parent = a_parent().with_nsdecls().element
@@ -320,18 +297,14 @@ def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
parent, zomChild, expected_xml = insert_fixture
parent._insert_zomChild(zomChild)
assert parent.xml == expected_xml
- assert parent._insert_zomChild.__doc__.startswith(
- "Return the passed ```` "
- )
+ assert parent._insert_zomChild.__doc__.startswith("Return the passed ```` ")
def it_adds_an_add_method_for_the_child_element(self, add_fixture):
parent, expected_xml = add_fixture
zomChild = parent._add_zomChild()
assert parent.xml == expected_xml
assert isinstance(zomChild, CT_ZomChild)
- assert parent._add_zomChild.__doc__.startswith(
- "Add a new ```` child element "
- )
+ assert parent._add_zomChild.__doc__.startswith("Add a new ```` child element ")
def it_removes_the_property_root_name_used_for_declaration(self):
assert not hasattr(CT_Parent, "zomChild")
@@ -393,17 +366,13 @@ def it_adds_an_add_method_for_the_child_element(self, add_fixture):
zooChild = parent._add_zooChild()
assert parent.xml == expected_xml
assert isinstance(zooChild, CT_ZooChild)
- assert parent._add_zooChild.__doc__.startswith(
- "Add a new ```` child element "
- )
+ assert parent._add_zooChild.__doc__.startswith("Add a new ```` child element ")
def it_adds_an_insert_method_for_the_child_element(self, insert_fixture):
parent, zooChild, expected_xml = insert_fixture
parent._insert_zooChild(zooChild)
assert parent.xml == expected_xml
- assert parent._insert_zooChild.__doc__.startswith(
- "Return the passed ```` "
- )
+ assert parent._insert_zooChild.__doc__.startswith("Return the passed ```` ")
def it_adds_a_get_or_add_method_for_the_child_element(self, get_or_add_fixture):
parent, expected_xml = get_or_add_fixture
@@ -522,9 +491,7 @@ class CT_Parent(BaseOxmlElement):
(Choice("p:choice"), Choice("p:choice2")),
successors=("p:oomChild", "p:oooChild"),
)
- oomChild = OneOrMore(
- "p:oomChild", successors=("p:oooChild", "p:zomChild", "p:zooChild")
- )
+ oomChild = OneOrMore("p:oomChild", successors=("p:oooChild", "p:zomChild", "p:zooChild"))
oooChild = OneAndOnlyOne("p:oooChild")
zomChild = ZeroOrMore("p:zomChild", successors=("p:zooChild",))
zooChild = ZeroOrOne("p:zooChild", successors=())
diff --git a/tests/oxml/unitdata/dml.py b/tests/oxml/unitdata/dml.py
index 5573116f2..8c716ab81 100644
--- a/tests/oxml/unitdata/dml.py
+++ b/tests/oxml/unitdata/dml.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""XML test data builders for pptx.oxml.dml unit tests."""
-"""
-XML test data builders for pptx.oxml.dml unit tests
-"""
-
-from __future__ import absolute_import, print_function
+from __future__ import annotations
from ...unitdata import BaseBuilder
diff --git a/tests/oxml/unitdata/shape.py b/tests/oxml/unitdata/shape.py
index 560657e8a..a5a39360a 100644
--- a/tests/oxml/unitdata/shape.py
+++ b/tests/oxml/unitdata/shape.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Test data for autoshape-related unit tests."""
+from __future__ import annotations
+
from ...unitdata import BaseBuilder
diff --git a/tests/oxml/unitdata/text.py b/tests/oxml/unitdata/text.py
index 23753fdc8..b86ff45d7 100644
--- a/tests/oxml/unitdata/text.py
+++ b/tests/oxml/unitdata/text.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""XML test data builders for `pptx.oxml.text` unit tests."""
-"""
-XML test data builders for pptx.oxml.text unit tests
-"""
-
-from __future__ import absolute_import, print_function
+from __future__ import annotations
from ...unitdata import BaseBuilder
diff --git a/tests/parts/test_chart.py b/tests/parts/test_chart.py
index ca7fe7771..b0a41f581 100644
--- a/tests/parts/test_chart.py
+++ b/tests/parts/test_chart.py
@@ -1,13 +1,14 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.parts.chart` module."""
+from __future__ import annotations
+
import pytest
from pptx.chart.chart import Chart
from pptx.chart.data import ChartData
from pptx.enum.chart import XL_CHART_TYPE as XCT
-from pptx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TYPE as RT
+from pptx.opc.constants import CONTENT_TYPE as CT
+from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.opc.package import OpcPackage
from pptx.opc.packuri import PackURI
from pptx.oxml.chart.chart import CT_ChartSpace
@@ -28,9 +29,7 @@ def it_can_construct_from_chart_type_and_data(self, request):
package_.next_partname.return_value = PackURI("/ppt/charts/chart42.xml")
chart_part_ = instance_mock(request, ChartPart)
# --- load() must have autospec turned off to work in Python 2.7 mock ---
- load_ = method_mock(
- request, ChartPart, "load", autospec=False, return_value=chart_part_
- )
+ load_ = method_mock(request, ChartPart, "load", autospec=False, return_value=chart_part_)
chart_part = ChartPart.new(XCT.RADAR, chart_data_, package_)
@@ -39,9 +38,7 @@ def it_can_construct_from_chart_type_and_data(self, request):
load_.assert_called_once_with(
"/ppt/charts/chart42.xml", CT.DML_CHART, package_, b"chart-blob"
)
- chart_part_.chart_workbook.update_from_xlsx_blob.assert_called_once_with(
- b"xlsx-blob"
- )
+ chart_part_.chart_workbook.update_from_xlsx_blob.assert_called_once_with(b"xlsx-blob")
assert chart_part is chart_part_
def it_provides_access_to_the_chart_object(self, request, chartSpace_):
@@ -129,9 +126,7 @@ def it_adds_an_xlsx_part_on_update_if_needed(
EmbeddedXlsxPart_.new.assert_called_once_with(b"xlsx-blob", package_)
xlsx_part_prop_.assert_called_with(xlsx_part_)
- def but_it_replaces_the_xlsx_blob_when_the_part_exists(
- self, xlsx_part_prop_, xlsx_part_
- ):
+ def but_it_replaces_the_xlsx_blob_when_the_part_exists(self, xlsx_part_prop_, xlsx_part_):
xlsx_part_prop_.return_value = xlsx_part_
chart_data = ChartWorkbook(None, None)
chart_data.update_from_xlsx_blob(b"xlsx-blob")
diff --git a/tests/parts/test_coreprops.py b/tests/parts/test_coreprops.py
index 3f20ca933..0983218e4 100644
--- a/tests/parts/test_coreprops.py
+++ b/tests/parts/test_coreprops.py
@@ -1,3 +1,5 @@
+# pyright: reportPrivateUsage=false
+
"""Unit-test suite for `pptx.parts.coreprops` module."""
from __future__ import annotations
@@ -14,68 +16,71 @@
class DescribeCorePropertiesPart(object):
"""Unit-test suite for `pptx.parts.coreprops.CorePropertiesPart` objects."""
- def it_knows_the_string_property_values(self, str_prop_get_fixture):
- core_properties, prop_name, expected_value = str_prop_get_fixture
- actual_value = getattr(core_properties, prop_name)
- assert actual_value == expected_value
-
- def it_can_change_the_string_property_values(self, str_prop_set_fixture):
- core_properties, prop_name, value, expected_xml = str_prop_set_fixture
- setattr(core_properties, prop_name, value)
- assert core_properties._element.xml == expected_xml
-
- def it_knows_the_date_property_values(self, date_prop_get_fixture):
- core_properties, prop_name, expected_datetime = date_prop_get_fixture
- actual_datetime = getattr(core_properties, prop_name)
- assert actual_datetime == expected_datetime
+ @pytest.mark.parametrize(
+ ("prop_name", "expected_value"),
+ [
+ ("author", "python-pptx"),
+ ("category", ""),
+ ("comments", ""),
+ ("content_status", "DRAFT"),
+ ("identifier", "GXS 10.2.1ab"),
+ ("keywords", "foo bar baz"),
+ ("language", "US-EN"),
+ ("last_modified_by", "Steve Canny"),
+ ("subject", "Spam"),
+ ("title", "Presentation"),
+ ("version", "1.2.88"),
+ ],
+ )
+ def it_knows_the_string_property_values(
+ self, core_properties: CorePropertiesPart, prop_name: str, expected_value: str
+ ):
+ assert getattr(core_properties, prop_name) == expected_value
+
+ @pytest.mark.parametrize(
+ ("prop_name", "tagname", "value"),
+ [
+ ("author", "dc:creator", "scanny"),
+ ("category", "cp:category", "silly stories"),
+ ("comments", "dc:description", "Bar foo to you"),
+ ("content_status", "cp:contentStatus", "FINAL"),
+ ("identifier", "dc:identifier", "GT 5.2.xab"),
+ ("keywords", "cp:keywords", "dog cat moo"),
+ ("language", "dc:language", "GB-EN"),
+ ("last_modified_by", "cp:lastModifiedBy", "Billy Bob"),
+ ("subject", "dc:subject", "Eggs"),
+ ("title", "dc:title", "Dissertation"),
+ ("version", "cp:version", "81.2.8"),
+ ],
+ )
+ def it_can_change_the_string_property_values(self, prop_name: str, tagname: str, value: str):
+ coreProperties = self.coreProperties_xml(None, None)
+ core_properties = CorePropertiesPart.load(None, None, None, coreProperties) # type: ignore
- def it_can_change_the_date_property_values(self, date_prop_set_fixture):
- core_properties, prop_name, value, expected_xml = date_prop_set_fixture
setattr(core_properties, prop_name, value)
- assert core_properties._element.xml == expected_xml
-
- def it_knows_the_revision_number(self, revision_get_fixture):
- core_properties, expected_revision = revision_get_fixture
- assert core_properties.revision == expected_revision
-
- def it_can_change_the_revision_number(self, revision_set_fixture):
- core_properties, revision, expected_xml = revision_set_fixture
- core_properties.revision = revision
- assert core_properties._element.xml == expected_xml
-
- def it_can_construct_a_default_core_props(self):
- core_props = CorePropertiesPart.default(None)
- # verify -----------------------
- assert isinstance(core_props, CorePropertiesPart)
- assert core_props.content_type is CT.OPC_CORE_PROPERTIES
- assert core_props.partname == "/docProps/core.xml"
- assert isinstance(core_props._element, CT_CoreProperties)
- assert core_props.title == "PowerPoint Presentation"
- assert core_props.last_modified_by == "python-pptx"
- assert core_props.revision == 1
- # core_props.modified only stores time with seconds resolution, so
- # comparison needs to be a little loose (within two seconds)
- modified_timedelta = (
- dt.datetime.now(dt.timezone.utc).replace(tzinfo=None) - core_props.modified
- )
- max_expected_timedelta = dt.timedelta(seconds=2)
- assert modified_timedelta < max_expected_timedelta
- # fixtures -------------------------------------------------------
+ assert core_properties._element.xml == self.coreProperties_xml(tagname, value)
- @pytest.fixture(
- params=[
+ @pytest.mark.parametrize(
+ ("prop_name", "expected_value"),
+ [
("created", dt.datetime(2012, 11, 17, 16, 37, 40)),
("last_printed", dt.datetime(2014, 6, 4, 4, 28)),
("modified", None),
- ]
+ ],
)
- def date_prop_get_fixture(self, request, core_properties):
- prop_name, expected_datetime = request.param
- return core_properties, prop_name, expected_datetime
+ def it_knows_the_date_property_values(
+ self,
+ core_properties: CorePropertiesPart,
+ prop_name: str,
+ expected_value: dt.datetime | None,
+ ):
+ actual_datetime = getattr(core_properties, prop_name)
+ assert actual_datetime == expected_value
- @pytest.fixture(
- params=[
+ @pytest.mark.parametrize(
+ ("prop_name", "tagname", "value", "str_val", "attrs"),
+ [
(
"created",
"dcterms:created",
@@ -97,75 +102,59 @@ def date_prop_get_fixture(self, request, core_properties):
"2005-04-03T02:01:00Z",
' xsi:type="dcterms:W3CDTF"',
),
- ]
- )
- def date_prop_set_fixture(self, request):
- prop_name, tagname, value, str_val, attrs = request.param
- coreProperties = self.coreProperties(None, None)
- core_properties = CorePropertiesPart.load(None, None, None, coreProperties)
- expected_xml = self.coreProperties(tagname, str_val, attrs)
- return core_properties, prop_name, value, expected_xml
-
- @pytest.fixture(
- params=[
- ("author", "python-pptx"),
- ("category", ""),
- ("comments", ""),
- ("content_status", "DRAFT"),
- ("identifier", "GXS 10.2.1ab"),
- ("keywords", "foo bar baz"),
- ("language", "US-EN"),
- ("last_modified_by", "Steve Canny"),
- ("subject", "Spam"),
- ("title", "Presentation"),
- ("version", "1.2.88"),
- ]
+ ],
)
- def str_prop_get_fixture(self, request, core_properties):
- prop_name, expected_value = request.param
- return core_properties, prop_name, expected_value
+ def it_can_change_the_date_property_values(
+ self, prop_name: str, tagname: str, value: dt.datetime, str_val: str, attrs: str
+ ):
+ coreProperties = self.coreProperties_xml(None, None)
+ core_properties = CorePropertiesPart.load(None, None, None, coreProperties) # type: ignore
- @pytest.fixture(
- params=[
- ("author", "dc:creator", "scanny"),
- ("category", "cp:category", "silly stories"),
- ("comments", "dc:description", "Bar foo to you"),
- ("content_status", "cp:contentStatus", "FINAL"),
- ("identifier", "dc:identifier", "GT 5.2.xab"),
- ("keywords", "cp:keywords", "dog cat moo"),
- ("language", "dc:language", "GB-EN"),
- ("last_modified_by", "cp:lastModifiedBy", "Billy Bob"),
- ("subject", "dc:subject", "Eggs"),
- ("title", "dc:title", "Dissertation"),
- ("version", "cp:version", "81.2.8"),
- ]
+ setattr(core_properties, prop_name, value)
+
+ assert core_properties._element.xml == self.coreProperties_xml(tagname, str_val, attrs)
+
+ @pytest.mark.parametrize(
+ ("str_val", "expected_value"),
+ [("42", 42), (None, 0), ("foobar", 0), ("-17", 0), ("32.7", 0)],
)
- def str_prop_set_fixture(self, request):
- prop_name, tagname, value = request.param
- coreProperties = self.coreProperties(None, None)
- core_properties = CorePropertiesPart.load(None, None, None, coreProperties)
- expected_xml = self.coreProperties(tagname, value)
- return core_properties, prop_name, value, expected_xml
-
- @pytest.fixture(params=[("42", 42), (None, 0), ("foobar", 0), ("-17", 0), ("32.7", 0)])
- def revision_get_fixture(self, request):
- str_val, expected_revision = request.param
+ def it_knows_the_revision_number(self, str_val: str | None, expected_value: int):
tagname = "" if str_val is None else "cp:revision"
- coreProperties = self.coreProperties(tagname, str_val)
- core_properties = CorePropertiesPart.load(None, None, None, coreProperties)
- return core_properties, expected_revision
+ coreProperties = self.coreProperties_xml(tagname, str_val)
+ core_properties = CorePropertiesPart.load(None, None, None, coreProperties) # type: ignore
+
+ assert core_properties.revision == expected_value
+
+ def it_can_change_the_revision_number(self):
+ coreProperties = self.coreProperties_xml(None, None)
+ core_properties = CorePropertiesPart.load(None, None, None, coreProperties) # type: ignore
+
+ core_properties.revision = 42
- @pytest.fixture(params=[(42, "42")])
- def revision_set_fixture(self, request):
- value, str_val = request.param
- coreProperties = self.coreProperties(None, None)
- core_properties = CorePropertiesPart.load(None, None, None, coreProperties)
- expected_xml = self.coreProperties("cp:revision", str_val)
- return core_properties, value, expected_xml
+ assert core_properties._element.xml == self.coreProperties_xml("cp:revision", "42")
+
+ def it_can_construct_a_default_core_props(self):
+ core_props = CorePropertiesPart.default(None) # type: ignore
+ # verify -----------------------
+ assert isinstance(core_props, CorePropertiesPart)
+ assert core_props.content_type is CT.OPC_CORE_PROPERTIES
+ assert core_props.partname == "/docProps/core.xml"
+ assert isinstance(core_props._element, CT_CoreProperties)
+ assert core_props.title == "PowerPoint Presentation"
+ assert core_props.last_modified_by == "python-pptx"
+ assert core_props.revision == 1
+ assert core_props.modified is not None
+ # core_props.modified only stores time with seconds resolution, so
+ # comparison needs to be a little loose (within two seconds)
+ modified_timedelta = (
+ dt.datetime.now(dt.timezone.utc).replace(tzinfo=None) - core_props.modified
+ )
+ max_expected_timedelta = dt.timedelta(seconds=2)
+ assert modified_timedelta < max_expected_timedelta
- # fixture components ---------------------------------------------
+ # -- fixtures ----------------------------------------------------
- def coreProperties(self, tagname, str_val, attrs=""):
+ def coreProperties_xml(self, tagname: str | None, str_val: str | None, attrs: str = "") -> str:
tmpl = (
'1.2.88\n"
b"\n"
)
- return CorePropertiesPart.load(None, None, None, xml)
+ return CorePropertiesPart.load(None, None, None, xml) # type: ignore
diff --git a/tests/parts/test_embeddedpackage.py b/tests/parts/test_embeddedpackage.py
index 1f368d557..ae2aca82f 100644
--- a/tests/parts/test_embeddedpackage.py
+++ b/tests/parts/test_embeddedpackage.py
@@ -1,35 +1,35 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.parts.embeddedpackage` module."""
+from __future__ import annotations
+
import pytest
from pptx.enum.shapes import PROG_ID
from pptx.opc.constants import CONTENT_TYPE as CT
from pptx.opc.package import OpcPackage, PackURI
from pptx.parts.embeddedpackage import (
- EmbeddedPackagePart,
EmbeddedDocxPart,
+ EmbeddedPackagePart,
EmbeddedPptxPart,
EmbeddedXlsxPart,
)
-from ..unitutil.mock import ANY, class_mock, initializer_mock, instance_mock
+from ..unitutil.mock import ANY, FixtureRequest, class_mock, initializer_mock, instance_mock
class DescribeEmbeddedPackagePart(object):
"""Unit-test suite for `pptx.parts.embeddedpackage.EmbeddedPackagePart` objects."""
@pytest.mark.parametrize(
- "prog_id, EmbeddedPartCls",
- (
+ ("prog_id", "EmbeddedPartCls"),
+ [
(PROG_ID.DOCX, EmbeddedDocxPart),
(PROG_ID.PPTX, EmbeddedPptxPart),
(PROG_ID.XLSX, EmbeddedXlsxPart),
- ),
+ ],
)
def it_provides_a_factory_that_creates_a_package_part_for_MS_Office_files(
- self, request, prog_id, EmbeddedPartCls
+ self, request: FixtureRequest, prog_id: PROG_ID, EmbeddedPartCls: type
):
object_blob_ = b"0123456789"
package_ = instance_mock(request, OpcPackage)
@@ -44,7 +44,7 @@ def it_provides_a_factory_that_creates_a_package_part_for_MS_Office_files(
EmbeddedPartCls_.new.assert_called_once_with(object_blob_, package_)
assert ole_object_part is embedded_object_part_
- def but_it_creates_a_generic_object_part_for_non_MS_Office_files(self, request):
+ def but_it_creates_a_generic_object_part_for_non_MS_Office_files(self, request: FixtureRequest):
progId = "Foo.Bar.42"
object_blob_ = b"0123456789"
package_ = instance_mock(request, OpcPackage)
@@ -54,15 +54,11 @@ def but_it_creates_a_generic_object_part_for_non_MS_Office_files(self, request):
ole_object_part = EmbeddedPackagePart.factory(progId, object_blob_, package_)
- package_.next_partname.assert_called_once_with(
- "/ppt/embeddings/oleObject%d.bin"
- )
- _init_.assert_called_once_with(
- ANY, partname_, CT.OFC_OLE_OBJECT, package_, object_blob_
- )
+ package_.next_partname.assert_called_once_with("/ppt/embeddings/oleObject%d.bin")
+ _init_.assert_called_once_with(ANY, partname_, CT.OFC_OLE_OBJECT, package_, object_blob_)
assert isinstance(ole_object_part, EmbeddedPackagePart)
- def it_provides_a_contructor_classmethod_for_subclasses(self, request):
+ def it_provides_a_contructor_classmethod_for_subclasses(self, request: FixtureRequest):
blob_ = b"0123456789"
package_ = instance_mock(request, OpcPackage)
_init_ = initializer_mock(request, EmbeddedXlsxPart, autospec=True)
@@ -71,9 +67,7 @@ def it_provides_a_contructor_classmethod_for_subclasses(self, request):
xlsx_part = EmbeddedXlsxPart.new(blob_, package_)
- package_.next_partname.assert_called_once_with(
- EmbeddedXlsxPart.partname_template
- )
+ package_.next_partname.assert_called_once_with(EmbeddedXlsxPart.partname_template)
_init_.assert_called_once_with(
xlsx_part, partname_, EmbeddedXlsxPart.content_type, package_, blob_
)
diff --git a/tests/parts/test_image.py b/tests/parts/test_image.py
index 8e1f68274..386e3fce9 100644
--- a/tests/parts/test_image.py
+++ b/tests/parts/test_image.py
@@ -1,10 +1,11 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.parts.image` module."""
+from __future__ import annotations
+
+import io
+
import pytest
-from pptx.compat import BytesIO
from pptx.package import Package
from pptx.parts.image import Image, ImagePart
from pptx.util import Emu
@@ -18,7 +19,6 @@
property_mock,
)
-
images_pptx_path = absjoin(test_file_dir, "with_images.pptx")
test_image_path = absjoin(test_file_dir, "python-icon.jpeg")
@@ -67,9 +67,7 @@ def it_provides_access_to_its_image(self, request, image_):
(3337, 9999, 3337, 9999),
),
)
- def it_can_scale_its_dimensions(
- self, width, height, expected_width, expected_height
- ):
+ def it_can_scale_its_dimensions(self, width, height, expected_width, expected_height):
with open(test_image_path, "rb") as f:
blob = f.read()
image_part = ImagePart(None, None, None, blob)
@@ -211,7 +209,7 @@ def filename_fixture(self, request):
def from_stream_fixture(self, from_blob_, image_):
with open(test_image_path, "rb") as f:
blob = f.read()
- image_file = BytesIO(blob)
+ image_file = io.BytesIO(blob)
from_blob_.return_value = image_
return image_file, blob, image_
diff --git a/tests/parts/test_media.py b/tests/parts/test_media.py
index f183d7c47..f7095f35d 100644
--- a/tests/parts/test_media.py
+++ b/tests/parts/test_media.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Unit test suite for `pptx.parts.media` module."""
+from __future__ import annotations
+
from pptx.media import Video
from pptx.package import Package
from pptx.parts.media import MediaPart
diff --git a/tests/parts/test_presentation.py b/tests/parts/test_presentation.py
index 7089e73de..edde4c44c 100644
--- a/tests/parts/test_presentation.py
+++ b/tests/parts/test_presentation.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.parts.presentation` module."""
+from __future__ import annotations
+
import pytest
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
@@ -62,9 +62,7 @@ def but_it_adds_a_notes_master_part_when_needed(
The notes master present case is just above.
"""
- NotesMasterPart_ = class_mock(
- request, "pptx.parts.presentation.NotesMasterPart"
- )
+ NotesMasterPart_ = class_mock(request, "pptx.parts.presentation.NotesMasterPart")
NotesMasterPart_.create_default.return_value = notes_master_part_
part_related_by_.side_effect = KeyError
prs_part = PresentationPart(None, None, package_, None)
@@ -72,9 +70,7 @@ def but_it_adds_a_notes_master_part_when_needed(
notes_master_part = prs_part.notes_master_part
NotesMasterPart_.create_default.assert_called_once_with(package_)
- relate_to_.assert_called_once_with(
- prs_part, notes_master_part_, RT.NOTES_MASTER
- )
+ relate_to_.assert_called_once_with(prs_part, notes_master_part_, RT.NOTES_MASTER)
assert notes_master_part is notes_master_part_
def it_provides_access_to_its_notes_master(self, request, notes_master_part_):
@@ -100,12 +96,8 @@ def it_provides_access_to_a_related_slide(self, request, slide_, related_part_):
related_part_.assert_called_once_with(prs_part, "rId42")
assert slide is slide_
- def it_provides_access_to_a_related_master(
- self, request, slide_master_, related_part_
- ):
- slide_master_part_ = instance_mock(
- request, SlideMasterPart, slide_master=slide_master_
- )
+ def it_provides_access_to_a_related_master(self, request, slide_master_, related_part_):
+ slide_master_part_ = instance_mock(request, SlideMasterPart, slide_master=slide_master_)
related_part_.return_value = slide_master_part_
prs_part = PresentationPart(None, None, None, None)
@@ -131,14 +123,10 @@ def it_can_save_the_package_to_a_file(self, package_):
PresentationPart(None, None, package_, None).save("prs.pptx")
package_.save.assert_called_once_with("prs.pptx")
- def it_can_add_a_new_slide(
- self, request, package_, slide_part_, slide_, relate_to_
- ):
+ def it_can_add_a_new_slide(self, request, package_, slide_part_, slide_, relate_to_):
slide_layout_ = instance_mock(request, SlideLayout)
partname = PackURI("/ppt/slides/slide9.xml")
- property_mock(
- request, PresentationPart, "_next_slide_partname", return_value=partname
- )
+ property_mock(request, PresentationPart, "_next_slide_partname", return_value=partname)
SlidePart_ = class_mock(request, "pptx.parts.presentation.SlidePart")
SlidePart_.new.return_value = slide_part_
relate_to_.return_value = "rId42"
@@ -181,9 +169,7 @@ def it_raises_on_slide_id_not_found(self, slide_part_, related_part_):
prs_part.slide_id(slide_part_)
@pytest.mark.parametrize("is_present", (True, False))
- def it_finds_a_slide_by_slide_id(
- self, is_present, slide_, slide_part_, related_part_
- ):
+ def it_finds_a_slide_by_slide_id(self, is_present, slide_, slide_part_, related_part_):
prs_elm = element(
"p:presentation/p:sldIdLst/(p:sldId{r:id=a,id=256},p:sldId{r:id="
"b,id=257},p:sldId{r:id=c,id=258})"
diff --git a/tests/parts/test_slide.py b/tests/parts/test_slide.py
index 58929d124..9eb2f11b0 100644
--- a/tests/parts/test_slide.py
+++ b/tests/parts/test_slide.py
@@ -1,14 +1,15 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.parts.slide` module."""
+from __future__ import annotations
+
import pytest
from pptx.chart.data import ChartData
from pptx.enum.chart import XL_CHART_TYPE as XCT
from pptx.enum.shapes import PROG_ID
from pptx.media import Video
-from pptx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TYPE as RT
+from pptx.opc.constants import CONTENT_TYPE as CT
+from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.opc.package import Part
from pptx.opc.packuri import PackURI
from pptx.oxml.slide import CT_NotesMaster, CT_NotesSlide, CT_Slide
@@ -44,9 +45,7 @@ class DescribeBaseSlidePart(object):
"""Unit-test suite for `pptx.parts.slide.BaseSlidePart` objects."""
def it_knows_its_name(self):
- slide_part = BaseSlidePart(
- None, None, None, element("p:sld/p:cSld{name=Foobar}")
- )
+ slide_part = BaseSlidePart(None, None, None, element("p:sld/p:cSld{name=Foobar}"))
assert slide_part.name == "Foobar"
def it_can_get_a_related_image_by_rId(self, request, image_part_):
@@ -65,9 +64,7 @@ def it_can_get_a_related_image_by_rId(self, request, image_part_):
def it_can_add_an_image_part(self, request, image_part_):
package_ = instance_mock(request, Package)
package_.get_or_add_image_part.return_value = image_part_
- relate_to_ = method_mock(
- request, BaseSlidePart, "relate_to", return_value="rId6"
- )
+ relate_to_ = method_mock(request, BaseSlidePart, "relate_to", return_value="rId6")
slide_part = BaseSlidePart(None, None, package_, None)
image_part, rId = slide_part.get_or_add_image_part("foobar.png")
@@ -87,9 +84,7 @@ def image_part_(self, request):
class DescribeNotesMasterPart(object):
"""Unit-test suite for `pptx.parts.slide.NotesMasterPart` objects."""
- def it_can_create_a_notes_master_part(
- self, request, package_, notes_master_part_, theme_part_
- ):
+ def it_can_create_a_notes_master_part(self, request, package_, notes_master_part_, theme_part_):
method_mock(
request,
NotesMasterPart,
@@ -124,9 +119,7 @@ def it_provides_access_to_its_notes_master(self, request):
NotesMaster_.assert_called_once_with(notesMaster, notes_master_part)
assert notes_master is notes_master_
- def it_creates_a_new_notes_master_part_to_help(
- self, request, package_, notes_master_part_
- ):
+ def it_creates_a_new_notes_master_part_to_help(self, request, package_, notes_master_part_):
NotesMasterPart_ = class_mock(
request, "pptx.parts.slide.NotesMasterPart", return_value=notes_master_part_
)
@@ -151,9 +144,7 @@ def it_creates_a_new_notes_master_part_to_help(
assert notes_master_part is notes_master_part_
def it_creates_a_new_theme_part_to_help(self, request, package_, theme_part_):
- XmlPart_ = class_mock(
- request, "pptx.parts.slide.XmlPart", return_value=theme_part_
- )
+ XmlPart_ = class_mock(request, "pptx.parts.slide.XmlPart", return_value=theme_part_)
theme_elm = element("p:theme")
method_mock(
request,
@@ -216,15 +207,11 @@ def it_can_create_a_notes_slide_part(
notes_slide_part = NotesSlidePart.new(package_, slide_part_)
- _add_notes_slide_part_.assert_called_once_with(
- package_, slide_part_, notes_master_part_
- )
+ _add_notes_slide_part_.assert_called_once_with(package_, slide_part_, notes_master_part_)
notes_slide_.clone_master_placeholders.assert_called_once_with(notes_master_)
assert notes_slide_part is notes_slide_part_
- def it_provides_access_to_the_notes_master(
- self, request, notes_master_, notes_master_part_
- ):
+ def it_provides_access_to_the_notes_master(self, request, notes_master_, notes_master_part_):
part_related_by_ = method_mock(
request, NotesSlidePart, "part_related_by", return_value=notes_master_part_
)
@@ -237,9 +224,7 @@ def it_provides_access_to_the_notes_master(
assert notes_master is notes_master_
def it_provides_access_to_its_notes_slide(self, request, notes_slide_):
- NotesSlide_ = class_mock(
- request, "pptx.parts.slide.NotesSlide", return_value=notes_slide_
- )
+ NotesSlide_ = class_mock(request, "pptx.parts.slide.NotesSlide", return_value=notes_slide_)
notes = element("p:notes")
notes_slide_part = NotesSlidePart(None, None, None, notes)
@@ -255,20 +240,14 @@ def it_adds_a_notes_slide_part_to_help(
request, "pptx.parts.slide.NotesSlidePart", return_value=notes_slide_part_
)
notes = element("p:notes")
- new_ = method_mock(
- request, CT_NotesSlide, "new", autospec=False, return_value=notes
- )
- package_.next_partname.return_value = PackURI(
- "/ppt/notesSlides/notesSlide42.xml"
- )
+ new_ = method_mock(request, CT_NotesSlide, "new", autospec=False, return_value=notes)
+ package_.next_partname.return_value = PackURI("/ppt/notesSlides/notesSlide42.xml")
notes_slide_part = NotesSlidePart._add_notes_slide_part(
package_, slide_part_, notes_master_part_
)
- package_.next_partname.assert_called_once_with(
- "/ppt/notesSlides/notesSlide%d.xml"
- )
+ package_.next_partname.assert_called_once_with("/ppt/notesSlides/notesSlide%d.xml")
new_.assert_called_once_with()
NotesSlidePart_.assert_called_once_with(
PackURI("/ppt/notesSlides/notesSlide42.xml"),
@@ -354,9 +333,7 @@ def it_can_add_an_embedded_ole_object_part(
request, SlidePart, "_blob_from_file", return_value=b"012345"
)
embedded_package_part_ = instance_mock(request, EmbeddedPackagePart)
- EmbeddedPackagePart_ = class_mock(
- request, "pptx.parts.slide.EmbeddedPackagePart"
- )
+ EmbeddedPackagePart_ = class_mock(request, "pptx.parts.slide.EmbeddedPackagePart")
EmbeddedPackagePart_.factory.return_value = embedded_package_part_
relate_to_.return_value = "rId9"
slide_part = SlidePart(None, None, package_, None)
@@ -364,9 +341,7 @@ def it_can_add_an_embedded_ole_object_part(
_rId = slide_part.add_embedded_ole_object_part(prog_id, "workbook.xlsx")
_blob_from_file_.assert_called_once_with(slide_part, "workbook.xlsx")
- EmbeddedPackagePart_.factory.assert_called_once_with(
- prog_id, b"012345", package_
- )
+ EmbeddedPackagePart_.factory.assert_called_once_with(prog_id, b"012345", package_)
relate_to_.assert_called_once_with(slide_part, embedded_package_part_, rel_type)
assert _rId == "rId9"
@@ -394,9 +369,7 @@ def it_can_create_a_new_slide_part(self, request, package_, relate_to_):
slide_part = SlidePart.new(partname, package_, slide_layout_part_)
- _init_.assert_called_once_with(
- slide_part, partname, CT.PML_SLIDE, package_, sld
- )
+ _init_.assert_called_once_with(slide_part, partname, CT.PML_SLIDE, package_, sld)
slide_part.relate_to.assert_called_once_with(
slide_part, slide_layout_part_, RT.SLIDE_LAYOUT
)
@@ -559,9 +532,7 @@ class DescribeSlideLayoutPart(object):
def it_provides_access_to_its_slide_master(self, request):
slide_master_ = instance_mock(request, SlideMaster)
- slide_master_part_ = instance_mock(
- request, SlideMasterPart, slide_master=slide_master_
- )
+ slide_master_part_ = instance_mock(request, SlideMasterPart, slide_master=slide_master_)
part_related_by_ = method_mock(
request, SlideLayoutPart, "part_related_by", return_value=slide_master_part_
)
@@ -604,9 +575,7 @@ def it_provides_access_to_its_slide_master(self, request):
def it_provides_access_to_a_related_slide_layout(self, request):
slide_layout_ = instance_mock(request, SlideLayout)
- slide_layout_part_ = instance_mock(
- request, SlideLayoutPart, slide_layout=slide_layout_
- )
+ slide_layout_part_ = instance_mock(request, SlideLayoutPart, slide_layout=slide_layout_)
related_part_ = method_mock(
request, SlideMasterPart, "related_part", return_value=slide_layout_part_
)
diff --git a/tests/shapes/test_autoshape.py b/tests/shapes/test_autoshape.py
index 9e6173caf..efb38e6b9 100644
--- a/tests/shapes/test_autoshape.py
+++ b/tests/shapes/test_autoshape.py
@@ -1,8 +1,10 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
-"""Test suite for pptx.shapes.autoshape module."""
+"""Unit-test suite for `pptx.shapes.autoshape` module."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, cast
import pytest
@@ -26,45 +28,76 @@
from ..unitutil.cxml import element, xml
from ..unitutil.mock import class_mock, instance_mock, property_mock
+if TYPE_CHECKING:
+ from pptx.spec import AdjustmentValue
-class DescribeAdjustment(object):
- def it_knows_its_effective_value(self, effective_val_fixture_):
- adjustment, expected_effective_value = effective_val_fixture_
- assert adjustment.effective_value == expected_effective_value
- # fixture --------------------------------------------------------
+class DescribeAdjustment(object):
+ """Unit-test suite for `pptx.shapes.autoshape.Adjustment`."""
- def _effective_adj_val_cases():
- return [
- # no actual, effective should be determined by default value
+ @pytest.mark.parametrize(
+ ("def_val", "actual", "expected_value"),
+ [
+ # -- no actual, effective should be determined by default value --
(50000, None, 0.5),
- # actual matches default
+ # -- actual matches default --
(50000, 50000, 0.5),
- # actual is different than default
+ # -- actual is different than default --
(50000, 12500, 0.125),
- # actual is zero
+ # -- actual is zero --
(50000, 0, 0.0),
- # negative default
+ # -- negative default --
(-20833, None, -0.20833),
- # negative actual
+ # -- negative actual --
(-20833, -5678901, -56.78901),
- ]
-
- @pytest.fixture(params=_effective_adj_val_cases())
- def effective_val_fixture_(self, request):
- name = None
- def_val, actual, expected_effective_value = request.param
- adjustment = Adjustment(name, def_val, actual)
- return adjustment, expected_effective_value
+ ],
+ )
+ def it_knows_its_effective_value(self, def_val: int, actual: int | None, expected_value: float):
+ assert Adjustment("foobar", def_val, actual).effective_value == expected_value
class DescribeAdjustmentCollection(object):
- def it_should_load_default_adjustment_values(self, prstGeom_cases_):
- prstGeom, prst, expected = prstGeom_cases_
+ """Unit-test suite for `pptx.shapes.autoshape.AdjustmentCollection`."""
+
+ @pytest.mark.parametrize(
+ ("prst", "expected_values"),
+ [
+ # -- rect has no adjustments --
+ ("rect", ()),
+ # -- chevron has one simple one
+ ("chevron", (("adj", 50000),)),
+ # -- one with several and some negative --
+ (
+ "accentBorderCallout1",
+ (("adj1", 18750), ("adj2", -8333), ("adj3", 112500), ("adj4", -38333)),
+ ),
+ # -- another one with some negative --
+ (
+ "wedgeRoundRectCallout",
+ (("adj1", -20833), ("adj2", 62500), ("adj3", 16667)),
+ ),
+ # -- one with values outside normal range --
+ (
+ "circularArrow",
+ (
+ ("adj1", 12500),
+ ("adj2", 1142319),
+ ("adj3", 20457681),
+ ("adj4", 10800000),
+ ("adj5", 12500),
+ ),
+ ),
+ ],
+ )
+ def it_should_load_default_adjustment_values(
+ self, prst: str, expected_values: tuple[str, tuple[tuple[str, int], ...]]
+ ):
+ prstGeom = cast(CT_PresetGeometry2D, element(f"a:prstGeom{{prst={prst}}}/a:avLst"))
+
adjustments = AdjustmentCollection(prstGeom)._adjustments
+
actuals = tuple([(adj.name, adj.def_val) for adj in adjustments])
- assert len(adjustments) == len(expected)
- assert actuals == expected
+ assert actuals == expected_values
def it_should_load_adj_val_actuals_from_xml(self, load_adj_actuals_fixture_):
prstGeom, expected_actuals, prstGeom_xml = load_adj_actuals_fixture_
@@ -187,41 +220,6 @@ def load_adj_actuals_fixture_(self, request):
prstGeom_xml = prstGeom_bldr.xml
return prstGeom, expected, prstGeom_xml
- def _prstGeom_cases():
- return [
- # rect has no adjustments
- ("rect", ()),
- # chevron has one simple one
- ("chevron", (("adj", 50000),)),
- # one with several and some negative
- (
- "accentBorderCallout1",
- (("adj1", 18750), ("adj2", -8333), ("adj3", 112500), ("adj4", -38333)),
- ),
- # another one with some negative
- (
- "wedgeRoundRectCallout",
- (("adj1", -20833), ("adj2", 62500), ("adj3", 16667)),
- ),
- # one with values outside normal range
- (
- "circularArrow",
- (
- ("adj1", 12500),
- ("adj2", 1142319),
- ("adj3", 20457681),
- ("adj4", 10800000),
- ("adj5", 12500),
- ),
- ),
- ]
-
- @pytest.fixture(params=_prstGeom_cases())
- def prstGeom_cases_(self, request):
- prst, expected_values = request.param
- prstGeom = a_prstGeom().with_nsdecls().with_prst(prst).with_child(an_avLst()).element
- return prstGeom, prst, expected_values
-
def _effective_val_cases():
return [
("rect", ()),
@@ -261,8 +259,26 @@ def it_xml_escapes_the_basename_when_the_name_contains_special_characters(self):
assert autoshape_type.prst == "noSmoking"
assert autoshape_type.basename == ""No" Symbol"
- def it_knows_the_default_adj_vals_for_its_autoshape_type(self, default_adj_vals_fixture_):
- prst, default_adj_vals = default_adj_vals_fixture_
+ @pytest.mark.parametrize(
+ ("prst", "default_adj_vals"),
+ [
+ (MSO_SHAPE.RECTANGLE, ()),
+ (MSO_SHAPE.CHEVRON, (("adj", 50000),)),
+ (
+ MSO_SHAPE.LEFT_CIRCULAR_ARROW,
+ (
+ ("adj1", 12500),
+ ("adj2", -1142319),
+ ("adj3", 1142319),
+ ("adj4", 10800000),
+ ("adj5", 12500),
+ ),
+ ),
+ ],
+ )
+ def it_knows_the_default_adj_vals_for_its_autoshape_type(
+ self, prst: MSO_SHAPE, default_adj_vals: tuple[AdjustmentValue, ...]
+ ):
_default_adj_vals = AutoShapeType.default_adjustment_values(prst)
assert _default_adj_vals == default_adj_vals
@@ -270,7 +286,7 @@ def it_knows_the_autoshape_type_id_for_each_prst_key(self):
assert AutoShapeType.id_from_prst("rect") == MSO_SHAPE.RECTANGLE
def it_raises_when_asked_for_autoshape_type_id_with_a_bad_prst(self):
- with pytest.raises(ValueError):
+ with pytest.raises(ValueError, match="MSO_AUTO_SHAPE_TYPE has no XML mapping for 'badPr"):
AutoShapeType.id_from_prst("badPrst")
def it_caches_autoshape_type_lookups(self):
@@ -283,29 +299,6 @@ def it_raises_on_construction_with_bad_autoshape_type_id(self):
with pytest.raises(KeyError):
AutoShapeType(9999)
- # fixtures -------------------------------------------------------
-
- def _default_adj_vals_cases():
- return [
- (MSO_SHAPE.RECTANGLE, ()),
- (MSO_SHAPE.CHEVRON, (("adj", 50000),)),
- (
- MSO_SHAPE.LEFT_CIRCULAR_ARROW,
- (
- ("adj1", 12500),
- ("adj2", -1142319),
- ("adj3", 1142319),
- ("adj4", 10800000),
- ("adj5", 12500),
- ),
- ),
- ]
-
- @pytest.fixture(params=_default_adj_vals_cases())
- def default_adj_vals_fixture_(self, request):
- prst, default_adj_vals = request.param
- return prst, default_adj_vals
-
class DescribeShape(object):
"""Unit-test suite for `pptx.shapes.autoshape.Shape` object."""
diff --git a/tests/shapes/test_base.py b/tests/shapes/test_base.py
index 8182323de..89632ca80 100644
--- a/tests/shapes/test_base.py
+++ b/tests/shapes/test_base.py
@@ -1,7 +1,11 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.shapes.base` module."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, cast
+
import pytest
from pptx.action import ActionSetting
@@ -34,6 +38,11 @@
from ..unitutil.cxml import element, xml
from ..unitutil.mock import class_mock, instance_mock, loose_mock
+if TYPE_CHECKING:
+ from pptx.opc.package import XmlPart
+ from pptx.oxml.shapes import ShapeElement
+ from pptx.types import ProvidesPart
+
class DescribeBaseShape(object):
"""Unit-test suite for `pptx.shapes.base.BaseShape` objects."""
@@ -57,10 +66,47 @@ def it_can_change_its_name(self, name_set_fixture):
shape.name = new_value
assert shape._element.xml == expected_xml
- def it_has_a_position(self, position_get_fixture):
- shape, expected_left, expected_top = position_get_fixture
- assert shape.left == expected_left
- assert shape.top == expected_top
+ @pytest.mark.parametrize(
+ ("shape_cxml", "expected_x", "expected_y"),
+ [
+ ("p:cxnSp/p:spPr", None, None),
+ ("p:cxnSp/p:spPr/a:xfrm", None, None),
+ ("p:cxnSp/p:spPr/a:xfrm/a:off{x=123,y=456}", 123, 456),
+ ("p:graphicFrame/p:xfrm", None, None),
+ ("p:graphicFrame/p:xfrm/a:off{x=123,y=456}", 123, 456),
+ ("p:grpSp/p:grpSpPr", None, None),
+ ("p:grpSp/p:grpSpPr/a:xfrm/a:off{x=123,y=456}", 123, 456),
+ ("p:pic/p:spPr", None, None),
+ ("p:pic/p:spPr/a:xfrm", None, None),
+ ("p:pic/p:spPr/a:xfrm/a:off{x=123,y=456}", 123, 456),
+ ("p:sp/p:spPr", None, None),
+ ("p:sp/p:spPr/a:xfrm", None, None),
+ ("p:sp/p:spPr/a:xfrm/a:off{x=123,y=456}", 123, 456),
+ ],
+ )
+ def it_has_a_position(
+ self,
+ shape_cxml: str,
+ expected_x: int | None,
+ expected_y: int | None,
+ provides_part: ProvidesPart,
+ ):
+ shape_elm = cast("ShapeElement", element(shape_cxml))
+
+ shape = BaseShape(shape_elm, provides_part)
+
+ assert shape.left == expected_x
+ assert shape.top == expected_y
+
+ @pytest.fixture
+ def provides_part(self) -> ProvidesPart:
+
+ class FakeProvidesPart:
+ @property
+ def part(self) -> XmlPart:
+ raise NotImplementedError
+
+ return FakeProvidesPart()
def it_can_change_its_position(self, position_set_fixture):
shape, left, top, expected_xml = position_set_fixture
@@ -272,28 +318,6 @@ def phfmt_fixture(self, _PlaceholderFormat_, placeholder_format_):
def phfmt_raise_fixture(self):
return BaseShape(element("p:sp/p:nvSpPr/p:nvPr"), None)
- @pytest.fixture(
- params=[
- ("sp", False),
- ("sp_with_off", True),
- ("pic", False),
- ("pic_with_off", True),
- ("graphicFrame", False),
- ("graphicFrame_with_off", True),
- ("grpSp", False),
- ("grpSp_with_off", True),
- ("cxnSp", False),
- ("cxnSp_with_off", True),
- ]
- )
- def position_get_fixture(self, request, left, top):
- shape_elm_fixt_name, expect_values = request.param
- shape_elm = request.getfixturevalue(shape_elm_fixt_name)
- shape = BaseShape(shape_elm, None)
- if not expect_values:
- left = top = None
- return shape, left, top
-
@pytest.fixture(
params=[
("sp", "sp_with_off"),
@@ -363,9 +387,7 @@ def shadow_fixture(self, request, ShadowFormat_, shadow_):
@pytest.fixture
def ActionSetting_(self, request, action_setting_):
- return class_mock(
- request, "pptx.shapes.base.ActionSetting", return_value=action_setting_
- )
+ return class_mock(request, "pptx.shapes.base.ActionSetting", return_value=action_setting_)
@pytest.fixture
def action_setting_(self, request):
@@ -381,9 +403,7 @@ def cxnSp_with_ext(self, width, height):
a_cxnSp()
.with_nsdecls()
.with_child(
- an_spPr().with_child(
- an_xfrm().with_child(an_ext().with_cx(width).with_cy(height))
- )
+ an_spPr().with_child(an_xfrm().with_child(an_ext().with_cx(width).with_cy(height)))
)
).element
@@ -393,9 +413,7 @@ def cxnSp_with_off(self, left, top):
a_cxnSp()
.with_nsdecls()
.with_child(
- an_spPr().with_child(
- an_xfrm().with_child(an_off().with_x(left).with_y(top))
- )
+ an_spPr().with_child(an_xfrm().with_child(an_off().with_x(left).with_y(top)))
)
).element
@@ -442,9 +460,7 @@ def grpSp_with_off(self, left, top):
a_grpSp()
.with_nsdecls("p", "a")
.with_child(
- a_grpSpPr().with_child(
- an_xfrm().with_child(an_off().with_x(left).with_y(top))
- )
+ a_grpSpPr().with_child(an_xfrm().with_child(an_off().with_x(left).with_y(top)))
)
).element
@@ -466,9 +482,7 @@ def pic_with_off(self, left, top):
a_pic()
.with_nsdecls()
.with_child(
- an_spPr().with_child(
- an_xfrm().with_child(an_off().with_x(left).with_y(top))
- )
+ an_spPr().with_child(an_xfrm().with_child(an_off().with_x(left).with_y(top)))
)
).element
@@ -478,9 +492,7 @@ def pic_with_ext(self, width, height):
a_pic()
.with_nsdecls()
.with_child(
- an_spPr().with_child(
- an_xfrm().with_child(an_ext().with_cx(width).with_cy(height))
- )
+ an_spPr().with_child(an_xfrm().with_child(an_ext().with_cx(width).with_cy(height)))
)
).element
@@ -536,9 +548,7 @@ def sp_with_ext(self, width, height):
an_sp()
.with_nsdecls()
.with_child(
- an_spPr().with_child(
- an_xfrm().with_child(an_ext().with_cx(width).with_cy(height))
- )
+ an_spPr().with_child(an_xfrm().with_child(an_ext().with_cx(width).with_cy(height)))
)
).element
@@ -548,9 +558,7 @@ def sp_with_off(self, left, top):
an_sp()
.with_nsdecls()
.with_child(
- an_spPr().with_child(
- an_xfrm().with_child(an_off().with_x(left).with_y(top))
- )
+ an_spPr().with_child(an_xfrm().with_child(an_off().with_x(left).with_y(top)))
)
).element
diff --git a/tests/shapes/test_connector.py b/tests/shapes/test_connector.py
index 3bafa9f96..f61f0a029 100644
--- a/tests/shapes/test_connector.py
+++ b/tests/shapes/test_connector.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
-
"""Unit test suite for pptx.shapes.connector module."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
diff --git a/tests/shapes/test_freeform.py b/tests/shapes/test_freeform.py
index 26ded32e2..dd5f53f0d 100644
--- a/tests/shapes/test_freeform.py
+++ b/tests/shapes/test_freeform.py
@@ -1,22 +1,27 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.shapes.freeform` module"""
+from __future__ import annotations
+
import pytest
from pptx.shapes.autoshape import Shape
from pptx.shapes.freeform import (
+ FreeformBuilder,
_BaseDrawingOperation,
_Close,
- FreeformBuilder,
_LineSegment,
_MoveTo,
)
from pptx.shapes.shapetree import SlideShapes
+from pptx.util import Emu, Mm
from ..unitutil.cxml import element, xml
from ..unitutil.file import snippet_seq
from ..unitutil.mock import (
+ FixtureRequest,
+ Mock,
call,
initializer_mock,
instance_mock,
@@ -28,23 +33,20 @@
class DescribeFreeformBuilder(object):
"""Unit-test suite for `pptx.shapes.freeform.FreeformBuilder` objects."""
- def it_provides_a_constructor(self, new_fixture):
- shapes_, start_x, start_y, x_scale, y_scale = new_fixture[:5]
- _init_, start_x_int, start_y_int = new_fixture[5:]
+ def it_provides_a_constructor(self, shapes_: Mock, _init_: Mock):
+ start_x, start_y, x_scale, y_scale = 99.56, 200.49, 4.2, 2.4
+ start_x_int, start_y_int = 100, 200
builder = FreeformBuilder.new(shapes_, start_x, start_y, x_scale, y_scale)
- _init_.assert_called_once_with(
- builder, shapes_, start_x_int, start_y_int, x_scale, y_scale
- )
+ _init_.assert_called_once_with(builder, shapes_, start_x_int, start_y_int, x_scale, y_scale)
assert isinstance(builder, FreeformBuilder)
- @pytest.mark.parametrize("close", (True, False))
- def it_can_add_straight_line_segments(self, request, close):
+ @pytest.mark.parametrize("close", [True, False])
+ def it_can_add_straight_line_segments(self, request: FixtureRequest, close: bool):
_add_line_segment_ = method_mock(request, FreeformBuilder, "_add_line_segment")
_add_close_ = method_mock(request, FreeformBuilder, "_add_close")
-
- builder = FreeformBuilder(None, None, None, None, None)
+ builder = FreeformBuilder(None, None, None, None, None) # type: ignore
return_value = builder.add_line_segments(((1, 2), (3, 4), (5, 6)), close)
@@ -56,8 +58,10 @@ def it_can_add_straight_line_segments(self, request, close):
assert _add_close_.call_args_list == ([call(builder)] if close else [])
assert return_value is builder
- def it_can_move_the_pen_location(self, move_to_fixture):
- builder, x, y, _MoveTo_new_, move_to_ = move_to_fixture
+ def it_can_move_the_pen_location(self, _MoveTo_new_: Mock, move_to_: Mock):
+ x, y = 42, 24
+ _MoveTo_new_.return_value = move_to_
+ builder = FreeformBuilder(None, None, None, None, None) # type: ignore
return_value = builder.move_to(x, y)
@@ -65,46 +69,99 @@ def it_can_move_the_pen_location(self, move_to_fixture):
assert builder._drawing_operations[-1] is move_to_
assert return_value is builder
- def it_can_build_the_specified_freeform_shape(self, convert_fixture):
- builder, origin_x, origin_y, sp = convert_fixture[:4]
- apply_operation_to_, calls, shape_ = convert_fixture[4:]
+ def it_can_build_the_specified_freeform_shape(
+ self,
+ shapes_: Mock,
+ apply_operation_to_: Mock,
+ _add_freeform_sp_: Mock,
+ _start_path_: Mock,
+ shape_: Mock,
+ ):
+ origin_x, origin_y = Mm(42), Mm(24)
+ sp, path = element("p:sp"), element("a:path")
+ drawing_ops = (
+ _LineSegment(None, None, None), # type: ignore
+ _LineSegment(None, None, None), # type: ignore
+ )
+ shapes_._shape_factory.return_value = shape_
+ _add_freeform_sp_.return_value = sp
+ _start_path_.return_value = path
+ builder = FreeformBuilder(shapes_, None, None, None, None) # type: ignore
+ builder._drawing_operations.extend(drawing_ops)
+ calls = [call(drawing_ops[0], path), call(drawing_ops[1], path)]
shape = builder.convert_to_shape(origin_x, origin_y)
- builder._add_freeform_sp.assert_called_once_with(builder, origin_x, origin_y)
- builder._start_path.assert_called_once_with(builder, sp)
+ _add_freeform_sp_.assert_called_once_with(builder, origin_x, origin_y)
+ _start_path_.assert_called_once_with(builder, sp)
assert apply_operation_to_.call_args_list == calls
- builder._shapes._shape_factory.assert_called_once_with(sp)
+ shapes_._shape_factory.assert_called_once_with(sp)
assert shape is shape_
- def it_knows_the_shape_x_offset(self, shape_offset_x_fixture):
- builder, expected_value = shape_offset_x_fixture
- x_offset = builder.shape_offset_x
- assert x_offset == expected_value
+ @pytest.mark.parametrize(
+ ("start_x", "xs", "expected_value"),
+ [
+ (Mm(0), (1, None, 2, 3), Mm(0)),
+ (Mm(6), (1, None, 2, 3), Mm(1)),
+ (Mm(50), (150, -5, None, 100), Mm(-5)),
+ ],
+ )
+ def it_knows_the_shape_x_offset(
+ self, start_x: int, xs: tuple[int | None, ...], expected_value: int
+ ):
+ builder = FreeformBuilder(None, start_x, None, None, None) # type: ignore
+ drawing_ops = [_Close() if x is None else _LineSegment(builder, Mm(x), Mm(0)) for x in xs]
+ builder._drawing_operations.extend(drawing_ops)
+
+ assert builder.shape_offset_x == expected_value
- def it_knows_the_shape_y_offset(self, shape_offset_y_fixture):
- builder, expected_value = shape_offset_y_fixture
- y_offset = builder.shape_offset_y
- assert y_offset == expected_value
+ @pytest.mark.parametrize(
+ ("start_y", "ys", "expected_value"),
+ [
+ (Mm(0), (2, None, 6, 8), Mm(0)),
+ (Mm(4), (2, None, 6, 8), Mm(2)),
+ (Mm(19), (213, -22, None, 100), Mm(-22)),
+ ],
+ )
+ def it_knows_the_shape_y_offset(
+ self, start_y: int, ys: tuple[int | None, ...], expected_value: int
+ ):
+ builder = FreeformBuilder(None, None, start_y, None, None) # type: ignore
+ drawing_ops = [_Close() if y is None else _LineSegment(builder, Mm(0), Mm(y)) for y in ys]
+ builder._drawing_operations.extend(drawing_ops)
+
+ assert builder.shape_offset_y == expected_value
- def it_adds_a_freeform_sp_to_help(self, sp_fixture):
- builder, origin_x, origin_y, spTree, expected_xml = sp_fixture
+ def it_adds_a_freeform_sp_to_help(
+ self, _left_prop_: Mock, _top_prop_: Mock, _width_prop_: Mock, _height_prop_: Mock
+ ):
+ origin_x, origin_y = Emu(42), Emu(24)
+ spTree = element("p:spTree")
+ shapes = SlideShapes(spTree, None) # type: ignore
+ _left_prop_.return_value, _top_prop_.return_value = Emu(12), Emu(34)
+ _width_prop_.return_value, _height_prop_.return_value = 56, 78
+ builder = FreeformBuilder(shapes, None, None, None, None) # type: ignore
+ expected_xml = snippet_seq("freeform")[0]
sp = builder._add_freeform_sp(origin_x, origin_y)
assert spTree.xml == expected_xml
assert sp is spTree.xpath("p:sp")[0]
- def it_adds_a_line_segment_to_help(self, add_seg_fixture):
- builder, x, y, _LineSegment_new_, line_segment_ = add_seg_fixture
+ def it_adds_a_line_segment_to_help(self, _LineSegment_new_: Mock, line_segment_: Mock):
+ x, y = 4, 2
+ _LineSegment_new_.return_value = line_segment_
+
+ builder = FreeformBuilder(None, None, None, None, None) # type: ignore
builder._add_line_segment(x, y)
_LineSegment_new_.assert_called_once_with(builder, x, y)
assert builder._drawing_operations == [line_segment_]
- def it_closes_a_contour_to_help(self, add_close_fixture):
- builder, _Close_new_, close_ = add_close_fixture
+ def it_closes_a_contour_to_help(self, _Close_new_: Mock, close_: Mock):
+ _Close_new_.return_value = close_
+ builder = FreeformBuilder(None, None, None, None, None) # type: ignore
builder._add_close()
@@ -126,8 +183,15 @@ def it_knows_the_freeform_width_to_help(self, width_fixture):
width = builder._width
assert width == expected_value
- def it_knows_the_freeform_height_to_help(self, height_fixture):
- builder, expected_value = height_fixture
+ @pytest.mark.parametrize(
+ ("dy", "y_scale", "expected_value"),
+ [(0, 2.0, 0), (24, 10.0, 240), (914400, 314.1, 287213040)],
+ )
+ def it_knows_the_freeform_height_to_help(
+ self, dy: int, y_scale: float, expected_value: int, _dy_prop_: Mock
+ ):
+ _dy_prop_.return_value = dy
+ builder = FreeformBuilder(None, None, None, None, y_scale) # type: ignore
height = builder._height
assert height == expected_value
@@ -141,7 +205,9 @@ def it_knows_the_local_coordinate_height_to_help(self, dy_fixture):
dy = builder._dy
assert dy == expected_value
- def it_can_start_a_new_path_to_help(self, request, _dx_prop_, _dy_prop_):
+ def it_can_start_a_new_path_to_help(
+ self, request: FixtureRequest, _dx_prop_: Mock, _dy_prop_: Mock
+ ):
_local_to_shape_ = method_mock(
request, FreeformBuilder, "_local_to_shape", return_value=(101, 202)
)
@@ -154,8 +220,7 @@ def it_can_start_a_new_path_to_help(self, request, _dx_prop_, _dy_prop_):
_local_to_shape_.assert_called_once_with(builder, start_x, start_y)
assert sp.xml == xml(
- "p:sp/p:spPr/a:custGeom/a:pathLst/a:path{w=1001,h=2002}/a:moveTo"
- "/a:pt{x=101,y=202}"
+ "p:sp/p:spPr/a:custGeom/a:pathLst/a:path{w=1001,h=2002}/a:moveTo" "/a:pt{x=101,y=202}"
)
assert path is sp.xpath(".//a:path")[-1]
@@ -166,39 +231,6 @@ def it_translates_local_to_shape_coordinates_to_help(self, local_fixture):
# fixtures -------------------------------------------------------
- @pytest.fixture
- def add_close_fixture(self, _Close_new_, close_):
- _Close_new_.return_value = close_
- builder = FreeformBuilder(None, None, None, None, None)
- return builder, _Close_new_, close_
-
- @pytest.fixture
- def add_seg_fixture(self, _LineSegment_new_, line_segment_):
- x, y = 4, 2
- _LineSegment_new_.return_value = line_segment_
-
- builder = FreeformBuilder(None, None, None, None, None)
- return builder, x, y, _LineSegment_new_, line_segment_
-
- @pytest.fixture
- def convert_fixture(
- self, shapes_, apply_operation_to_, _add_freeform_sp_, _start_path_, shape_
- ):
- origin_x, origin_y = 42, 24
- sp, path = element("p:sp"), element("a:path")
- drawing_ops = (
- _BaseDrawingOperation(None, None, None),
- _BaseDrawingOperation(None, None, None),
- )
- shapes_._shape_factory.return_value = shape_
- _add_freeform_sp_.return_value = sp
- _start_path_.return_value = path
-
- builder = FreeformBuilder(shapes_, None, None, None, None)
- builder._drawing_operations.extend(drawing_ops)
- calls = [call(drawing_ops[0], path), call(drawing_ops[1], path)]
- return (builder, origin_x, origin_y, sp, apply_operation_to_, calls, shape_)
-
@pytest.fixture(
params=[
(0, (1, None, 2, 3), 3),
@@ -206,7 +238,7 @@ def convert_fixture(
(50, (150, -5, None, 100), 155),
]
)
- def dx_fixture(self, request):
+ def dx_fixture(self, request: FixtureRequest):
start_x, xs, expected_value = request.param
drawing_ops = []
for x in xs:
@@ -226,7 +258,7 @@ def dx_fixture(self, request):
(32, (160, -8, None, 101), 168),
]
)
- def dy_fixture(self, request):
+ def dy_fixture(self, request: FixtureRequest):
start_y, ys, expected_value = request.param
drawing_ops = []
for y in ys:
@@ -239,16 +271,8 @@ def dy_fixture(self, request):
builder._drawing_operations.extend(drawing_ops)
return builder, expected_value
- @pytest.fixture(params=[(0, 2.0, 0), (24, 10.0, 240), (914400, 314.1, 287213040)])
- def height_fixture(self, request, _dy_prop_):
- dy, y_scale, expected_value = request.param
- _dy_prop_.return_value = dy
-
- builder = FreeformBuilder(None, None, None, None, y_scale)
- return builder, expected_value
-
@pytest.fixture(params=[(0, 1.0, 0), (4, 10.0, 40), (914400, 914.3, 836035920)])
- def left_fixture(self, request, shape_offset_x_prop_):
+ def left_fixture(self, request: FixtureRequest, shape_offset_x_prop_: Mock):
offset_x, x_scale, expected_value = request.param
shape_offset_x_prop_.return_value = offset_x
@@ -256,7 +280,7 @@ def left_fixture(self, request, shape_offset_x_prop_):
return builder, expected_value
@pytest.fixture
- def local_fixture(self, shape_offset_x_prop_, shape_offset_y_prop_):
+ def local_fixture(self, shape_offset_x_prop_: Mock, shape_offset_y_prop_: Mock):
local_x, local_y = 123, 456
shape_offset_x_prop_.return_value = 23
shape_offset_y_prop_.return_value = 156
@@ -266,70 +290,9 @@ def local_fixture(self, shape_offset_x_prop_, shape_offset_y_prop_):
return builder, local_x, local_y, expected_value
@pytest.fixture
- def move_to_fixture(self, _MoveTo_new_, move_to_):
- x, y = 42, 24
- _MoveTo_new_.return_value = move_to_
-
- builder = FreeformBuilder(None, None, None, None, None)
- return builder, x, y, _MoveTo_new_, move_to_
-
- @pytest.fixture
- def new_fixture(self, shapes_, _init_):
- start_x, start_y, x_scale, y_scale = 99.56, 200.49, 4.2, 2.4
- start_x_int, start_y_int = 100, 200
- return (
- shapes_,
- start_x,
- start_y,
- x_scale,
- y_scale,
- _init_,
- start_x_int,
- start_y_int,
- )
-
- @pytest.fixture(
- params=[
- (0, (1, None, 2, 3), 0),
- (6, (1, None, 2, 3), 1),
- (50, (150, -5, None, 100), -5),
- ]
- )
- def shape_offset_x_fixture(self, request):
- start_x, xs, expected_value = request.param
- drawing_ops = []
- for x in xs:
- if x is None:
- drawing_ops.append(_Close())
- else:
- drawing_ops.append(_BaseDrawingOperation(None, x, None))
-
- builder = FreeformBuilder(None, start_x, None, None, None)
- builder._drawing_operations.extend(drawing_ops)
- return builder, expected_value
-
- @pytest.fixture(
- params=[
- (0, (2, None, 6, 8), 0),
- (4, (2, None, 6, 8), 2),
- (19, (213, -22, None, 100), -22),
- ]
- )
- def shape_offset_y_fixture(self, request):
- start_y, ys, expected_value = request.param
- drawing_ops = []
- for y in ys:
- if y is None:
- drawing_ops.append(_Close())
- else:
- drawing_ops.append(_BaseDrawingOperation(None, None, y))
-
- builder = FreeformBuilder(None, None, start_y, None, None)
- builder._drawing_operations.extend(drawing_ops)
- return builder, expected_value
-
- @pytest.fixture
- def sp_fixture(self, _left_prop_, _top_prop_, _width_prop_, _height_prop_):
+ def sp_fixture(
+ self, _left_prop_: Mock, _top_prop_: Mock, _width_prop_: Mock, _height_prop_: Mock
+ ):
origin_x, origin_y = 42, 24
spTree = element("p:spTree")
shapes = SlideShapes(spTree, None)
@@ -340,10 +303,8 @@ def sp_fixture(self, _left_prop_, _top_prop_, _width_prop_, _height_prop_):
expected_xml = snippet_seq("freeform")[0]
return builder, origin_x, origin_y, spTree, expected_xml
- @pytest.fixture(
- params=[(0, 11.0, 0), (100, 10.36, 1036), (914242, 943.1, 862221630)]
- )
- def top_fixture(self, request, shape_offset_y_prop_):
+ @pytest.fixture(params=[(0, 11.0, 0), (100, 10.36, 1036), (914242, 943.1, 862221630)])
+ def top_fixture(self, request: FixtureRequest, shape_offset_y_prop_: Mock):
offset_y, y_scale, expected_value = request.param
shape_offset_y_prop_.return_value = offset_y
@@ -351,7 +312,7 @@ def top_fixture(self, request, shape_offset_y_prop_):
return builder, expected_value
@pytest.fixture(params=[(0, 1.0, 0), (42, 10.0, 420), (914400, 914.4, 836127360)])
- def width_fixture(self, request, _dx_prop_):
+ def width_fixture(self, request: FixtureRequest, _dx_prop_: Mock):
dx, x_scale, expected_value = request.param
_dx_prop_.return_value = dx
@@ -361,85 +322,83 @@ def width_fixture(self, request, _dx_prop_):
# fixture components -----------------------------------
@pytest.fixture
- def _add_freeform_sp_(self, request):
+ def _add_freeform_sp_(self, request: FixtureRequest):
return method_mock(request, FreeformBuilder, "_add_freeform_sp", autospec=True)
@pytest.fixture
- def apply_operation_to_(self, request):
- return method_mock(
- request, _BaseDrawingOperation, "apply_operation_to", autospec=True
- )
+ def apply_operation_to_(self, request: FixtureRequest):
+ return method_mock(request, _LineSegment, "apply_operation_to", autospec=True)
@pytest.fixture
- def close_(self, request):
+ def close_(self, request: FixtureRequest):
return instance_mock(request, _Close)
@pytest.fixture
- def _Close_new_(self, request):
+ def _Close_new_(self, request: FixtureRequest):
return method_mock(request, _Close, "new", autospec=False)
@pytest.fixture
- def _dx_prop_(self, request):
+ def _dx_prop_(self, request: FixtureRequest):
return property_mock(request, FreeformBuilder, "_dx")
@pytest.fixture
- def _dy_prop_(self, request):
+ def _dy_prop_(self, request: FixtureRequest):
return property_mock(request, FreeformBuilder, "_dy")
@pytest.fixture
- def _height_prop_(self, request):
+ def _height_prop_(self, request: FixtureRequest):
return property_mock(request, FreeformBuilder, "_height")
@pytest.fixture
- def _init_(self, request):
+ def _init_(self, request: FixtureRequest):
return initializer_mock(request, FreeformBuilder, autospec=True)
@pytest.fixture
- def _left_prop_(self, request):
+ def _left_prop_(self, request: FixtureRequest):
return property_mock(request, FreeformBuilder, "_left")
@pytest.fixture
- def line_segment_(self, request):
+ def line_segment_(self, request: FixtureRequest):
return instance_mock(request, _LineSegment)
@pytest.fixture
- def _LineSegment_new_(self, request):
+ def _LineSegment_new_(self, request: FixtureRequest):
return method_mock(request, _LineSegment, "new", autospec=False)
@pytest.fixture
- def move_to_(self, request):
+ def move_to_(self, request: FixtureRequest):
return instance_mock(request, _MoveTo)
@pytest.fixture
- def _MoveTo_new_(self, request):
+ def _MoveTo_new_(self, request: FixtureRequest):
return method_mock(request, _MoveTo, "new", autospec=False)
@pytest.fixture
- def shape_(self, request):
+ def shape_(self, request: FixtureRequest):
return instance_mock(request, Shape)
@pytest.fixture
- def shape_offset_x_prop_(self, request):
+ def shape_offset_x_prop_(self, request: FixtureRequest):
return property_mock(request, FreeformBuilder, "shape_offset_x")
@pytest.fixture
- def shape_offset_y_prop_(self, request):
+ def shape_offset_y_prop_(self, request: FixtureRequest):
return property_mock(request, FreeformBuilder, "shape_offset_y")
@pytest.fixture
- def shapes_(self, request):
+ def shapes_(self, request: FixtureRequest):
return instance_mock(request, SlideShapes)
@pytest.fixture
- def _start_path_(self, request):
+ def _start_path_(self, request: FixtureRequest):
return method_mock(request, FreeformBuilder, "_start_path", autospec=True)
@pytest.fixture
- def _top_prop_(self, request):
+ def _top_prop_(self, request: FixtureRequest):
return property_mock(request, FreeformBuilder, "_top")
@pytest.fixture
- def _width_prop_(self, request):
+ def _width_prop_(self, request: FixtureRequest):
return property_mock(request, FreeformBuilder, "_width")
@@ -508,7 +467,7 @@ def apply_fixture(self):
return close, path, expected_xml
@pytest.fixture
- def _init_(self, request):
+ def _init_(self, request: FixtureRequest):
return initializer_mock(request, _Close, autospec=True)
@@ -551,11 +510,11 @@ def new_fixture(self, builder_, _init_):
# fixture components -----------------------------------
@pytest.fixture
- def builder_(self, request):
+ def builder_(self, request: FixtureRequest):
return instance_mock(request, FreeformBuilder)
@pytest.fixture
- def _init_(self, request):
+ def _init_(self, request: FixtureRequest):
return initializer_mock(request, _LineSegment, autospec=True)
@@ -598,9 +557,9 @@ def new_fixture(self, builder_, _init_):
# fixture components -----------------------------------
@pytest.fixture
- def builder_(self, request):
+ def builder_(self, request: FixtureRequest):
return instance_mock(request, FreeformBuilder)
@pytest.fixture
- def _init_(self, request):
+ def _init_(self, request: FixtureRequest):
return initializer_mock(request, _MoveTo, autospec=True)
diff --git a/tests/shapes/test_graphfrm.py b/tests/shapes/test_graphfrm.py
index 5f2250111..3324fcfe0 100644
--- a/tests/shapes/test_graphfrm.py
+++ b/tests/shapes/test_graphfrm.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Unit-test suite for pptx.shapes.graphfrm module."""
+from __future__ import annotations
+
import pytest
from pptx.chart.chart import Chart
@@ -62,9 +62,7 @@ def it_provides_access_to_its_chart_part(self, request, chart_part_):
),
)
def it_knows_whether_it_contains_a_chart(self, graphicData_uri, expected_value):
- graphicFrame = element(
- "p:graphicFrame/a:graphic/a:graphicData{uri=%s}" % graphicData_uri
- )
+ graphicFrame = element("p:graphicFrame/a:graphic/a:graphicData{uri=%s}" % graphicData_uri)
assert GraphicFrame(graphicFrame, None).has_chart is expected_value
@pytest.mark.parametrize(
@@ -76,9 +74,7 @@ def it_knows_whether_it_contains_a_chart(self, graphicData_uri, expected_value):
),
)
def it_knows_whether_it_contains_a_table(self, graphicData_uri, expected_value):
- graphicFrame = element(
- "p:graphicFrame/a:graphic/a:graphicData{uri=%s}" % graphicData_uri
- )
+ graphicFrame = element("p:graphicFrame/a:graphic/a:graphicData{uri=%s}" % graphicData_uri)
assert GraphicFrame(graphicFrame, None).has_table is expected_value
def it_provides_access_to_the_OleFormat_object(self, request):
@@ -127,10 +123,7 @@ def it_raises_on_shadow(self):
)
def it_knows_its_shape_type(self, uri, oleObj_child, expected_value):
graphicFrame = element(
- (
- "p:graphicFrame/a:graphic/a:graphicData{uri=%s}/p:oleObj/p:%s"
- % (uri, oleObj_child)
- )
+ ("p:graphicFrame/a:graphic/a:graphicData{uri=%s}/p:oleObj/p:%s" % (uri, oleObj_child))
if oleObj_child
else "p:graphicFrame/a:graphic/a:graphicData{uri=%s}" % uri
)
diff --git a/tests/shapes/test_group.py b/tests/shapes/test_group.py
index f9e1248d4..93c06d029 100644
--- a/tests/shapes/test_group.py
+++ b/tests/shapes/test_group.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
-
"""Test suite for pptx.shapes.group module."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
diff --git a/tests/shapes/test_picture.py b/tests/shapes/test_picture.py
index 3be7c6b89..75728da21 100644
--- a/tests/shapes/test_picture.py
+++ b/tests/shapes/test_picture.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
-
"""Test suite for pptx.shapes.picture module."""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
@@ -10,7 +8,7 @@
from pptx.enum.shapes import MSO_SHAPE, MSO_SHAPE_TYPE, PP_MEDIA_TYPE
from pptx.parts.image import Image
from pptx.parts.slide import SlidePart
-from pptx.shapes.picture import _BasePicture, _MediaFormat, Movie, Picture
+from pptx.shapes.picture import Movie, Picture, _BasePicture, _MediaFormat
from pptx.util import Pt
from ..unitutil.cxml import element, xml
@@ -206,9 +204,7 @@ def image_(self, request):
@pytest.fixture
def _MediaFormat_(self, request, media_format_):
- return class_mock(
- request, "pptx.shapes.picture._MediaFormat", return_value=media_format_
- )
+ return class_mock(request, "pptx.shapes.picture._MediaFormat", return_value=media_format_)
@pytest.fixture
def media_format_(self, request):
diff --git a/tests/shapes/test_placeholder.py b/tests/shapes/test_placeholder.py
index 75b0814ca..4d9b26ea0 100644
--- a/tests/shapes/test_placeholder.py
+++ b/tests/shapes/test_placeholder.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.shapes.placeholder` module."""
+from __future__ import annotations
+
import pytest
from pptx.chart.data import ChartData
@@ -12,9 +12,7 @@
from pptx.parts.slide import NotesSlidePart, SlidePart
from pptx.shapes.placeholder import (
BasePlaceholder,
- _BaseSlidePlaceholder,
ChartPlaceholder,
- _InheritsDimensions,
LayoutPlaceholder,
MasterPlaceholder,
NotesSlidePlaceholder,
@@ -22,6 +20,8 @@
PlaceholderGraphicFrame,
PlaceholderPicture,
TablePlaceholder,
+ _BaseSlidePlaceholder,
+ _InheritsDimensions,
)
from pptx.shapes.shapetree import NotesSlidePlaceholders
from pptx.slide import NotesMaster, SlideLayout, SlideMaster
@@ -151,9 +151,7 @@ def layout_placeholder_(self, request):
@pytest.fixture
def part_prop_(self, request, slide_part_):
- return property_mock(
- request, _BaseSlidePlaceholder, "part", return_value=slide_part_
- )
+ return property_mock(request, _BaseSlidePlaceholder, "part", return_value=slide_part_)
@pytest.fixture
def slide_layout_(self, request):
@@ -205,9 +203,7 @@ def idx_fixture(self, request):
placeholder = BasePlaceholder(shape_elm, None)
return placeholder, expected_idx
- @pytest.fixture(
- params=[(None, ST_Direction.HORZ), (ST_Direction.VERT, ST_Direction.VERT)]
- )
+ @pytest.fixture(params=[(None, ST_Direction.HORZ), (ST_Direction.VERT, ST_Direction.VERT)])
def orient_fixture(self, request):
orient, expected_orient = request.param
ph_bldr = a_ph()
@@ -279,9 +275,7 @@ def shape_elm_factory(tagname, ph_type, idx):
"pic": a_ph().with_type("pic").with_idx(idx),
"tbl": a_ph().with_type("tbl").with_idx(idx),
}[ph_type]
- return (
- root_bldr.with_child(nvXxPr_bldr.with_child(an_nvPr().with_child(ph_bldr)))
- ).element
+ return (root_bldr.with_child(nvXxPr_bldr.with_child(an_nvPr().with_child(ph_bldr)))).element
class DescribeChartPlaceholder(object):
@@ -439,9 +433,7 @@ def notes_slide_part_(self, request):
@pytest.fixture
def part_prop_(self, request, notes_slide_part_):
- return property_mock(
- request, NotesSlidePlaceholder, "part", return_value=notes_slide_part_
- )
+ return property_mock(request, NotesSlidePlaceholder, "part", return_value=notes_slide_part_)
class DescribePicturePlaceholder(object):
@@ -482,10 +474,7 @@ def it_creates_a_pic_element_to_help(self, request, image_size, crop_attr_names)
return_value=(42, "bar", image_size),
)
picture_ph = PicturePlaceholder(
- element(
- "p:sp/(p:nvSpPr/p:cNvPr{id=2,name=foo},p:spPr/a:xfrm/a:ext{cx=99"
- ",cy=99})"
- ),
+ element("p:sp/(p:nvSpPr/p:cNvPr{id=2,name=foo},p:spPr/a:xfrm/a:ext{cx=99" ",cy=99})"),
None,
)
diff --git a/tests/shapes/test_shapetree.py b/tests/shapes/test_shapetree.py
index 63c1ee290..3cf1ab225 100644
--- a/tests/shapes/test_shapetree.py
+++ b/tests/shapes/test_shapetree.py
@@ -1,18 +1,21 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit test suite for pptx.shapes.shapetree module"""
+from __future__ import annotations
+
+import io
+
import pytest
-from pptx.compat import BytesIO
from pptx.chart.data import ChartData
from pptx.enum.chart import XL_CHART_TYPE
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE, MSO_CONNECTOR, PP_PLACEHOLDER, PROG_ID
+from pptx.media import SPEAKER_IMAGE_BYTES, Video
from pptx.oxml import parse_xml
from pptx.oxml.shapes.groupshape import CT_GroupShape
from pptx.oxml.shapes.picture import CT_Picture
from pptx.oxml.shapes.shared import BaseShapeElement, ST_Direction
-from pptx.media import SPEAKER_IMAGE_BYTES, Video
from pptx.parts.image import ImagePart
from pptx.parts.slide import SlidePart
from pptx.shapes.autoshape import AutoShapeType, Shape
@@ -23,32 +26,32 @@
from pptx.shapes.group import GroupShape
from pptx.shapes.picture import Movie, Picture
from pptx.shapes.placeholder import (
- _BaseSlidePlaceholder,
LayoutPlaceholder,
MasterPlaceholder,
NotesSlidePlaceholder,
+ _BaseSlidePlaceholder,
)
from pptx.shapes.shapetree import (
- _BaseGroupShapes,
BasePlaceholders,
BaseShapeFactory,
- _BaseShapes,
GroupShapes,
LayoutPlaceholders,
- _LayoutShapeFactory,
LayoutShapes,
MasterPlaceholders,
- _MasterShapeFactory,
MasterShapes,
- _MoviePicElementCreator,
NotesSlidePlaceholders,
- _NotesSlideShapeFactory,
NotesSlideShapes,
- _OleObjectElementCreator,
- _SlidePlaceholderFactory,
SlidePlaceholders,
SlideShapeFactory,
SlideShapes,
+ _BaseGroupShapes,
+ _BaseShapes,
+ _LayoutShapeFactory,
+ _MasterShapeFactory,
+ _MoviePicElementCreator,
+ _NotesSlideShapeFactory,
+ _OleObjectElementCreator,
+ _SlidePlaceholderFactory,
)
from pptx.slide import SlideLayout, SlideMaster
from pptx.table import Table
@@ -218,8 +221,7 @@ def len_fixture(self):
("p:spTree/p:nvSpPr/(p:cNvPr{id=foo},p:cNvPr{id=2})", 3),
("p:spTree/p:nvSpPr/(p:cNvPr{id=1fo},p:cNvPr{id=2})", 3),
(
- "p:spTree/p:nvSpPr/(p:cNvPr{id=1},p:cNvPr{id=1},p:"
- "cNvPr{id=1},p:cNvPr{id=4})",
+ "p:spTree/p:nvSpPr/(p:cNvPr{id=1},p:cNvPr{id=1},p:" "cNvPr{id=1},p:cNvPr{id=4})",
5,
),
]
@@ -244,9 +246,7 @@ def next_id_fixture(self, request):
)
def ph_name_fixture(self, request):
ph_type, sp_id, orient, expected_name = request.param
- spTree = element(
- "p:spTree/(p:cNvPr{name=Title 1},p:cNvPr{name=Table Placeholder " "3})"
- )
+ spTree = element("p:spTree/(p:cNvPr{name=Title 1},p:cNvPr{name=Table Placeholder " "3})")
shapes = SlideShapes(spTree, None)
return shapes, ph_type, sp_id, orient, expected_name
@@ -264,8 +264,7 @@ def turbo_fixture(self, request):
("p:spTree/p:nvSpPr/p:cNvPr{id=2}", True),
("p:spTree/p:nvSpPr/(p:cNvPr{id=1},p:cNvPr{id=3})", False),
(
- "p:spTree/p:nvSpPr/(p:cNvPr{id=1},p:cNvPr{id=1},p:"
- "cNvPr{id=1},p:cNvPr{id=4})",
+ "p:spTree/p:nvSpPr/(p:cNvPr{id=1},p:cNvPr{id=1},p:" "cNvPr{id=1},p:cNvPr{id=4})",
True,
),
]
@@ -319,9 +318,7 @@ def it_can_add_a_chart(
graphic_frame = shapes.add_chart(XL_CHART_TYPE.PIE, x, y, cx, cy, chart_data_)
- shapes.part.add_chart_part.assert_called_once_with(
- XL_CHART_TYPE.PIE, chart_data_
- )
+ shapes.part.add_chart_part.assert_called_once_with(XL_CHART_TYPE.PIE, chart_data_)
_add_chart_graphicFrame_.assert_called_once_with(shapes, "rId42", x, y, cx, cy)
_recalculate_extents_.assert_called_once_with(shapes)
_shape_factory_.assert_called_once_with(shapes, graphicFrame)
@@ -347,9 +344,7 @@ def it_can_provide_a_freeform_builder(self, freeform_fixture):
builder = shapes.build_freeform(start_x, start_y, scale)
- FreeformBuilder_new_.assert_called_once_with(
- shapes, start_x, start_y, x_scale, y_scale
- )
+ FreeformBuilder_new_.assert_called_once_with(shapes, start_x, start_y, x_scale, y_scale)
assert builder is builder_
def it_can_add_a_group_shape(self, group_fixture):
@@ -622,9 +617,7 @@ def add_textbox_sp_fixture(self, _next_shape_id_prop_):
return shapes, x, y, cx, cy, expected_xml
@pytest.fixture
- def connector_fixture(
- self, _add_cxnSp_, _shape_factory_, _recalculate_extents_, connector_
- ):
+ def connector_fixture(self, _add_cxnSp_, _shape_factory_, _recalculate_extents_, connector_):
shapes = _BaseGroupShapes(element("p:spTree"), None)
connector_type = MSO_CONNECTOR.STRAIGHT
begin_x, begin_y, end_x, end_y = 1, 2, 3, 4
@@ -766,9 +759,7 @@ def shape_fixture(
)
@pytest.fixture
- def textbox_fixture(
- self, _add_textbox_sp_, _recalculate_extents_, _shape_factory_, shape_
- ):
+ def textbox_fixture(self, _add_textbox_sp_, _recalculate_extents_, _shape_factory_, shape_):
shapes = _BaseGroupShapes(None, None)
x, y, cx, cy = 31, 32, 33, 34
sp = element("p:sp")
@@ -782,9 +773,7 @@ def textbox_fixture(
@pytest.fixture
def _add_chart_graphicFrame_(self, request):
- return method_mock(
- request, _BaseGroupShapes, "_add_chart_graphicFrame", autospec=True
- )
+ return method_mock(request, _BaseGroupShapes, "_add_chart_graphicFrame", autospec=True)
@pytest.fixture
def _add_cxnSp_(self, request):
@@ -792,9 +781,7 @@ def _add_cxnSp_(self, request):
@pytest.fixture
def _add_pic_from_image_part_(self, request):
- return method_mock(
- request, _BaseGroupShapes, "_add_pic_from_image_part", autospec=True
- )
+ return method_mock(request, _BaseGroupShapes, "_add_pic_from_image_part", autospec=True)
@pytest.fixture
def _add_sp_(self, request):
@@ -854,9 +841,7 @@ def picture_(self, request):
@pytest.fixture
def _recalculate_extents_(self, request):
- return method_mock(
- request, _BaseGroupShapes, "_recalculate_extents", autospec=True
- )
+ return method_mock(request, _BaseGroupShapes, "_recalculate_extents", autospec=True)
@pytest.fixture
def shape_(self, request):
@@ -1260,9 +1245,7 @@ def it_can_add_a_movie(self, movie_fixture):
_MoviePicElementCreator_, movie_pic = movie_fixture[9:11]
_add_video_timing_, _shape_factory_, movie_ = movie_fixture[11:]
- movie = shapes.add_movie(
- movie_file, x, y, cx, cy, poster_frame_image, mime_type
- )
+ movie = shapes.add_movie(movie_file, x, y, cx, cy, poster_frame_image, mime_type)
_MoviePicElementCreator_.new_movie_pic.assert_called_once_with(
shapes, shape_id_, movie_file, x, y, cx, cy, poster_frame_image, mime_type
@@ -1419,15 +1402,11 @@ def movie_(self, request):
@pytest.fixture
def _MoviePicElementCreator_(self, request):
- return class_mock(
- request, "pptx.shapes.shapetree._MoviePicElementCreator", autospec=True
- )
+ return class_mock(request, "pptx.shapes.shapetree._MoviePicElementCreator", autospec=True)
@pytest.fixture
def _next_shape_id_prop_(self, request, shape_id_):
- return property_mock(
- request, SlideShapes, "_next_shape_id", return_value=shape_id_
- )
+ return property_mock(request, SlideShapes, "_next_shape_id", return_value=shape_id_)
@pytest.fixture
def placeholder_(self, request):
@@ -1554,9 +1533,7 @@ def parent_(self, request):
@pytest.fixture
def ph_bldr(self):
- return an_sp().with_child(
- an_nvSpPr().with_child(an_nvPr().with_child(a_ph().with_idx(1)))
- )
+ return an_sp().with_child(an_nvSpPr().with_child(an_nvPr().with_child(a_ph().with_idx(1))))
class DescribeLayoutPlaceholders(object):
@@ -1679,9 +1656,7 @@ def master_placeholder_(self, request):
@pytest.fixture
def ph_bldr(self):
- return an_sp().with_child(
- an_nvSpPr().with_child(an_nvPr().with_child(a_ph().with_idx(1)))
- )
+ return an_sp().with_child(an_nvSpPr().with_child(an_nvPr().with_child(a_ph().with_idx(1))))
@pytest.fixture
def slide_master_(self, request):
@@ -1848,17 +1823,35 @@ def it_adds_the_poster_frame_image_to_help(self, pfrm_rId_fixture):
poster_frame_rId = movie_pic_element_creator._poster_frame_rId
- slide_part_.get_or_add_image_part.assert_called_once_with(
- poster_frame_image_file
- )
+ slide_part_.get_or_add_image_part.assert_called_once_with(poster_frame_image_file)
assert poster_frame_rId == expected_value
- def it_gets_the_poster_frame_image_file_to_help(self, pfrm_img_fixture):
- movie_pic_element_creator, BytesIO_ = pfrm_img_fixture[:2]
- calls, expected_value = pfrm_img_fixture[2:]
+ def it_gets_the_poster_frame_image_from_the_specified_path_to_help(
+ self, request: pytest.FixtureRequest
+ ):
+ BytesIO_ = class_mock(request, "pptx.shapes.shapetree.io.BytesIO")
+ movie_pic_element_creator = _MoviePicElementCreator(
+ None, None, None, None, None, None, None, "image.png", None # type: ignore
+ )
+
image_file = movie_pic_element_creator._poster_frame_image_file
- assert BytesIO_.call_args_list == calls
- assert image_file == expected_value
+
+ BytesIO_.assert_not_called()
+ assert image_file == "image.png"
+
+ def but_it_gets_the_poster_frame_image_from_the_default_bytes_when_None_specified(
+ self, request: pytest.FixtureRequest
+ ):
+ stream_ = instance_mock(request, io.BytesIO)
+ BytesIO_ = class_mock(request, "pptx.shapes.shapetree.io.BytesIO", return_value=stream_)
+ movie_pic_element_creator = _MoviePicElementCreator(
+ None, None, None, None, None, None, None, None, None # type: ignore
+ )
+
+ image_file = movie_pic_element_creator._poster_frame_image_file
+
+ BytesIO_.assert_called_once_with(SPEAKER_IMAGE_BYTES)
+ assert image_file == stream_
def it_gets_the_video_part_rIds_to_help(self, part_rIds_fixture):
movie_pic_element_creator, slide_part_ = part_rIds_fixture[:2]
@@ -1886,9 +1879,7 @@ def media_rId_fixture(self, _video_part_rIds_prop_):
return movie_pic_element_creator, expected_value
@pytest.fixture
- def movie_pic_fixture(
- self, shapes_, _MoviePicElementCreator_init_, _pic_prop_, pic_
- ):
+ def movie_pic_fixture(self, shapes_, _MoviePicElementCreator_init_, _pic_prop_, pic_):
shape_id, movie_file, x, y, cx, cy = 42, "movie.mp4", 1, 2, 3, 4
poster_frame_image, mime_type = "image.png", "video/mp4"
return (
@@ -1917,25 +1908,8 @@ def part_rIds_fixture(self, slide_part_, video_, _slide_part_prop_, _video_prop_
_video_prop_.return_value = video_
return (movie_pic_element_creator, slide_part_, video_, media_rId, video_rId)
- @pytest.fixture(params=["image.png", None])
- def pfrm_img_fixture(self, request, BytesIO_, stream_):
- poster_frame_file = request.param
- movie_pic_element_creator = _MoviePicElementCreator(
- None, None, None, None, None, None, None, poster_frame_file, None
- )
- if poster_frame_file is None:
- calls = [call(SPEAKER_IMAGE_BYTES)]
- BytesIO_.return_value = stream_
- expected_value = stream_
- else:
- calls = []
- expected_value = poster_frame_file
- return movie_pic_element_creator, BytesIO_, calls, expected_value
-
@pytest.fixture
- def pfrm_rId_fixture(
- self, _slide_part_prop_, slide_part_, _poster_frame_image_file_prop_
- ):
+ def pfrm_rId_fixture(self, _slide_part_prop_, slide_part_, _poster_frame_image_file_prop_):
movie_pic_element_creator = _MoviePicElementCreator(
None, None, None, None, None, None, None, None, None
)
@@ -2021,10 +1995,6 @@ def video_fixture(self, video_, from_path_or_file_like_):
# fixture components ---------------------------------------------
- @pytest.fixture
- def BytesIO_(self, request):
- return class_mock(request, "pptx.shapes.shapetree.BytesIO")
-
@pytest.fixture
def from_path_or_file_like_(self, request):
return method_mock(request, Video, "from_path_or_file_like", autospec=False)
@@ -2047,15 +2017,11 @@ def pic_(self):
@pytest.fixture
def _pic_prop_(self, request, pic_):
- return property_mock(
- request, _MoviePicElementCreator, "_pic", return_value=pic_
- )
+ return property_mock(request, _MoviePicElementCreator, "_pic", return_value=pic_)
@pytest.fixture
def _poster_frame_image_file_prop_(self, request):
- return property_mock(
- request, _MoviePicElementCreator, "_poster_frame_image_file"
- )
+ return property_mock(request, _MoviePicElementCreator, "_poster_frame_image_file")
@pytest.fixture
def _poster_frame_rId_prop_(self, request):
@@ -2077,10 +2043,6 @@ def slide_part_(self, request):
def _slide_part_prop_(self, request):
return property_mock(request, _MoviePicElementCreator, "_slide_part")
- @pytest.fixture
- def stream_(self, request):
- return instance_mock(request, BytesIO)
-
@pytest.fixture
def video_(self, request):
return instance_mock(request, Video)
@@ -2145,18 +2107,10 @@ def it_provides_a_graphicFrame_interface_method(self, request, shapes_):
def it_creates_the_graphicFrame_element(self, request):
shape_id, x, y, cx, cy = 7, 1, 2, 3, 4
- property_mock(
- request, _OleObjectElementCreator, "_shape_name", return_value="Object 42"
- )
- property_mock(
- request, _OleObjectElementCreator, "_ole_object_rId", return_value="rId42"
- )
- property_mock(
- request, _OleObjectElementCreator, "_progId", return_value="Excel.Sheet.42"
- )
- property_mock(
- request, _OleObjectElementCreator, "_icon_rId", return_value="rId24"
- )
+ property_mock(request, _OleObjectElementCreator, "_shape_name", return_value="Object 42")
+ property_mock(request, _OleObjectElementCreator, "_ole_object_rId", return_value="rId42")
+ property_mock(request, _OleObjectElementCreator, "_progId", return_value="Excel.Sheet.42")
+ property_mock(request, _OleObjectElementCreator, "_icon_rId", return_value="rId24")
property_mock(request, _OleObjectElementCreator, "_cx", return_value=cx)
property_mock(request, _OleObjectElementCreator, "_cy", return_value=cy)
element_creator = _OleObjectElementCreator(
@@ -2248,7 +2202,10 @@ def it_determines_the_shape_height_to_help(self, cy_arg, prog_id, expected_value
@pytest.mark.parametrize(
"icon_height_arg, expected_value",
- ((Emu(666666), Emu(666666)), (None, Emu(609600)),),
+ (
+ (Emu(666666), Emu(666666)),
+ (None, Emu(609600)),
+ ),
)
def it_determines_the_icon_height_to_help(self, icon_height_arg, expected_value):
element_creator = _OleObjectElementCreator(
@@ -2266,9 +2223,7 @@ def it_determines_the_icon_height_to_help(self, icon_height_arg, expected_value)
(None, PROG_ID.XLSX, "xlsx-icon.emf"),
),
)
- def it_resolves_the_icon_image_file_to_help(
- self, icon_file_arg, prog_id, expected_value
- ):
+ def it_resolves_the_icon_image_file_to_help(self, icon_file_arg, prog_id, expected_value):
element_creator = _OleObjectElementCreator(
None, None, None, prog_id, None, None, None, None, icon_file_arg, None, None
)
diff --git a/tests/test_action.py b/tests/test_action.py
index 33877eeae..dd0193ca6 100644
--- a/tests/test_action.py
+++ b/tests/test_action.py
@@ -1,15 +1,13 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.action` module."""
-from __future__ import unicode_literals
+from __future__ import annotations
import pytest
from pptx.action import ActionSetting, Hyperlink
from pptx.enum.action import PP_ACTION
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
-from pptx.opc.package import Part
+from pptx.opc.package import XmlPart
from pptx.parts.slide import SlidePart
from pptx.slide import Slide
@@ -51,8 +49,7 @@ def it_can_change_its_slide_jump_target(
_clear_click_action_.assert_called_once_with(action_setting)
part_.relate_to.assert_called_once_with(slide_part_, RT.SLIDE)
assert action_setting._element.xml == xml(
- "p:cNvPr{a:b=c,r:s=t}/a:hlinkClick{action=ppaction://hlinksldjump,r:id=rI"
- "d42}",
+ "p:cNvPr{a:b=c,r:s=t}/a:hlinkClick{action=ppaction://hlinksldjump,r:id=rI" "d42}",
)
def but_it_clears_the_target_slide_if_None_is_assigned(self, _clear_click_action_):
@@ -209,9 +206,7 @@ def target_get_fixture(self, request, action_prop_, _slide_index_prop_, part_pro
return action_setting, expected_value
@pytest.fixture(params=[(PP_ACTION.NEXT_SLIDE, 2), (PP_ACTION.PREVIOUS_SLIDE, 0)])
- def target_raise_fixture(
- self, request, action_prop_, part_prop_, _slide_index_prop_
- ):
+ def target_raise_fixture(self, request, action_prop_, part_prop_, _slide_index_prop_):
action_type, slide_idx = request.param
action_setting = ActionSetting(None, None)
action_prop_.return_value = action_type
@@ -240,7 +235,7 @@ def hyperlink_(self, request):
@pytest.fixture
def part_(self, request):
- return instance_mock(request, Part)
+ return instance_mock(request, XmlPart)
@pytest.fixture
def part_prop_(self, request):
diff --git a/tests/test_api.py b/tests/test_api.py
index b44573031..a48f48912 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for `pptx.api` module."""
-"""
-Test suite for pptx.api module
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import os
diff --git a/tests/test_media.py b/tests/test_media.py
index 9e42db9e5..be72f6e0e 100644
--- a/tests/test_media.py
+++ b/tests/test_media.py
@@ -1,16 +1,16 @@
-# encoding: utf-8
-
"""Unit test suite for `pptx.media` module."""
+from __future__ import annotations
+
+import io
+
import pytest
-from pptx.compat import BytesIO
from pptx.media import Video
from .unitutil.file import absjoin, test_file_dir
from .unitutil.mock import initializer_mock, instance_mock, method_mock, property_mock
-
TEST_VIDEO_PATH = absjoin(test_file_dir, "dummy.mp4")
@@ -87,9 +87,7 @@ def ext_fixture(self, request):
video = Video(None, mime_type, filename)
return video, expected_value
- @pytest.fixture(
- params=[("foobar.mp4", None, "foobar.mp4"), (None, "vid", "movie.vid")]
- )
+ @pytest.fixture(params=[("foobar.mp4", None, "foobar.mp4"), (None, "vid", "movie.vid")])
def filename_fixture(self, request, ext_prop_):
filename, ext, expected_value = request.param
video = Video(None, None, filename)
@@ -105,7 +103,7 @@ def from_blob_fixture(self, Video_init_):
def from_stream_fixture(self, video_, from_blob_):
with open(TEST_VIDEO_PATH, "rb") as f:
blob = f.read()
- movie_stream = BytesIO(blob)
+ movie_stream = io.BytesIO(blob)
mime_type = "video/mp4"
from_blob_.return_value = video_
return movie_stream, mime_type, blob, video_
diff --git a/tests/test_package.py b/tests/test_package.py
index 5e32e74ce..ee02af2d6 100644
--- a/tests/test_package.py
+++ b/tests/test_package.py
@@ -1,7 +1,9 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.package` module."""
+from __future__ import annotations
+
import os
import pytest
@@ -11,12 +13,11 @@
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.opc.package import Part, _Relationship
from pptx.opc.packuri import PackURI
-from pptx.package import _ImageParts, _MediaParts, Package
+from pptx.package import Package, _ImageParts, _MediaParts
from pptx.parts.coreprops import CorePropertiesPart
from pptx.parts.image import Image, ImagePart
from pptx.parts.media import MediaPart
-
from .unitutil.mock import call, class_mock, instance_mock, method_mock, property_mock
@@ -159,9 +160,7 @@ def it_can_iterate_over_the_package_image_parts(self, iter_fixture):
image_parts, expected_parts = iter_fixture
assert list(image_parts) == expected_parts
- def it_can_get_a_matching_image_part(
- self, Image_, image_, image_part_, _find_by_sha1_
- ):
+ def it_can_get_a_matching_image_part(self, Image_, image_, image_part_, _find_by_sha1_):
Image_.from_file.return_value = image_
_find_by_sha1_.return_value = image_part_
image_parts = _ImageParts(None)
diff --git a/tests/test_presentation.py b/tests/test_presentation.py
index 03d2b027a..7c5315143 100644
--- a/tests/test_presentation.py
+++ b/tests/test_presentation.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Unit-test suite for `pptx.presentation` module."""
-"""
-Test suite for pptx.presentation module.
-"""
-
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import annotations
import pytest
@@ -71,9 +67,7 @@ def it_provides_access_to_its_slide_master(self, master_fixture):
def it_provides_access_to_its_slide_masters(self, masters_fixture):
prs, SlideMasters_, slide_masters_, expected_xml = masters_fixture
slide_masters = prs.slide_masters
- SlideMasters_.assert_called_once_with(
- prs._element.xpath("p:sldMasterIdLst")[0], prs
- )
+ SlideMasters_.assert_called_once_with(prs._element.xpath("p:sldMasterIdLst")[0], prs)
assert slide_masters is slide_masters_
assert prs._element.xml == expected_xml
@@ -93,9 +87,7 @@ def core_props_fixture(self, prs_part_, core_properties_):
@pytest.fixture
def layouts_fixture(self, masters_prop_, slide_layouts_):
prs = Presentation(None, None)
- masters_prop_.return_value.__getitem__.return_value.slide_layouts = (
- slide_layouts_
- )
+ masters_prop_.return_value.__getitem__.return_value.slide_layouts = slide_layouts_
return prs, slide_layouts_
@pytest.fixture
@@ -134,9 +126,7 @@ def save_fixture(self, prs_part_):
file_ = "foobar.docx"
return prs, file_, prs_part_
- @pytest.fixture(
- params=[("p:presentation", None), ("p:presentation/p:sldSz{cy=42}", 42)]
- )
+ @pytest.fixture(params=[("p:presentation", None), ("p:presentation/p:sldSz{cy=42}", 42)])
def sld_height_get_fixture(self, request):
prs_cxml, expected_value = request.param
prs = Presentation(element(prs_cxml), None)
@@ -154,9 +144,7 @@ def sld_height_set_fixture(self, request):
expected_xml = xml(expected_cxml)
return prs, 914400, expected_xml
- @pytest.fixture(
- params=[("p:presentation", None), ("p:presentation/p:sldSz{cx=42}", 42)]
- )
+ @pytest.fixture(params=[("p:presentation", None), ("p:presentation/p:sldSz{cx=42}", 42)])
def sld_width_get_fixture(self, request):
prs_cxml, expected_value = request.param
prs = Presentation(element(prs_cxml), None)
@@ -224,9 +212,7 @@ def slide_layouts_(self, request):
@pytest.fixture
def SlideMasters_(self, request, slide_masters_):
- return class_mock(
- request, "pptx.presentation.SlideMasters", return_value=slide_masters_
- )
+ return class_mock(request, "pptx.presentation.SlideMasters", return_value=slide_masters_)
@pytest.fixture
def slide_master_(self, request):
diff --git a/tests/test_shared.py b/tests/test_shared.py
index e2d6bdc01..72a0ebc0e 100644
--- a/tests/test_shared.py
+++ b/tests/test_shared.py
@@ -1,7 +1,7 @@
-# encoding: utf-8
-
"""Unit-test suite for `pptx.shared` module."""
+from __future__ import annotations
+
import pytest
from pptx.opc.package import XmlPart
diff --git a/tests/test_slide.py b/tests/test_slide.py
index d4a1bdeef..74b528c3b 100644
--- a/tests/test_slide.py
+++ b/tests/test_slide.py
@@ -1,7 +1,9 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.slide` module."""
+from __future__ import annotations
+
import pytest
from pptx.dml.fill import FillFormat
@@ -23,9 +25,6 @@
SlideShapes,
)
from pptx.slide import (
- _Background,
- _BaseMaster,
- _BaseSlide,
NotesMaster,
NotesSlide,
Slide,
@@ -34,6 +33,9 @@
SlideMaster,
SlideMasters,
Slides,
+ _Background,
+ _BaseMaster,
+ _BaseSlide,
)
from pptx.text.text import TextFrame
@@ -71,9 +73,7 @@ def background_fixture(self, _Background_, background_):
_Background_.return_value = background_
return slide, _Background_, cSld, background_
- @pytest.fixture(
- params=[("p:sld/p:cSld", ""), ("p:sld/p:cSld{name=Foobar}", "Foobar")]
- )
+ @pytest.fixture(params=[("p:sld/p:cSld", ""), ("p:sld/p:cSld{name=Foobar}", "Foobar")])
def name_get_fixture(self, request):
sld_cxml, expected_name = request.param
base_slide = _BaseSlide(element(sld_cxml), None)
@@ -149,9 +149,7 @@ def subclass_fixture(self):
@pytest.fixture
def MasterPlaceholders_(self, request, placeholders_):
- return class_mock(
- request, "pptx.slide.MasterPlaceholders", return_value=placeholders_
- )
+ return class_mock(request, "pptx.slide.MasterPlaceholders", return_value=placeholders_)
@pytest.fixture
def MasterShapes_(self, request, shapes_):
@@ -169,9 +167,7 @@ def shapes_(self, request):
class DescribeNotesSlide(object):
"""Unit-test suite for `pptx.slide.NotesSlide` objects."""
- def it_can_clone_the_notes_master_placeholders(
- self, request, notes_master_, shapes_
- ):
+ def it_can_clone_the_notes_master_placeholders(self, request, notes_master_, shapes_):
placeholders = notes_master_.placeholders = (
BaseShape(element("p:sp/p:nvSpPr/p:nvPr/p:ph{type=body}"), None),
BaseShape(element("p:sp/p:nvSpPr/p:nvPr/p:ph{type=dt}"), None),
@@ -233,9 +229,7 @@ def notes_ph_fixture(self, request, placeholders_prop_):
return notes_slide, expected_value
@pytest.fixture(params=[True, False])
- def notes_tf_fixture(
- self, request, notes_placeholder_prop_, placeholder_, text_frame_
- ):
+ def notes_tf_fixture(self, request, notes_placeholder_prop_, placeholder_, text_frame_):
has_text_frame = request.param
notes_slide = NotesSlide(None, None)
if has_text_frame:
@@ -269,15 +263,11 @@ def notes_master_(self, request):
@pytest.fixture
def notes_placeholder_prop_(self, request, placeholder_):
- return property_mock(
- request, NotesSlide, "notes_placeholder", return_value=placeholder_
- )
+ return property_mock(request, NotesSlide, "notes_placeholder", return_value=placeholder_)
@pytest.fixture
def NotesSlidePlaceholders_(self, request, placeholders_):
- return class_mock(
- request, "pptx.slide.NotesSlidePlaceholders", return_value=placeholders_
- )
+ return class_mock(request, "pptx.slide.NotesSlidePlaceholders", return_value=placeholders_)
@pytest.fixture
def NotesSlideShapes_(self, request, shapes_):
@@ -293,9 +283,7 @@ def placeholders_(self, request):
@pytest.fixture
def placeholders_prop_(self, request, placeholders_):
- return property_mock(
- request, NotesSlide, "placeholders", return_value=placeholders_
- )
+ return property_mock(request, NotesSlide, "placeholders", return_value=placeholders_)
@pytest.fixture
def shapes_(self, request):
@@ -436,9 +424,7 @@ def placeholders_(self, request):
@pytest.fixture
def SlidePlaceholders_(self, request, placeholders_):
- return class_mock(
- request, "pptx.slide.SlidePlaceholders", return_value=placeholders_
- )
+ return class_mock(request, "pptx.slide.SlidePlaceholders", return_value=placeholders_)
@pytest.fixture
def SlideShapes_(self, request, shapes_):
@@ -616,9 +602,7 @@ def it_can_iterate_its_clonable_placeholders(self, cloneable_fixture):
cloneable = list(slide_layout.iter_cloneable_placeholders())
assert cloneable == expected_placeholders
- def it_provides_access_to_its_placeholders(
- self, LayoutPlaceholders_, placeholders_
- ):
+ def it_provides_access_to_its_placeholders(self, LayoutPlaceholders_, placeholders_):
sldLayout = element("p:sldLayout/p:cSld/p:spTree")
spTree = sldLayout.xpath("//p:spTree")[0]
slide_layout = SlideLayout(sldLayout, None)
@@ -675,9 +659,7 @@ def it_knows_which_slides_are_based_on_it(
((PP_PLACEHOLDER.SLIDE_NUMBER, PP_PLACEHOLDER.FOOTER), ()),
]
)
- def cloneable_fixture(
- self, request, placeholders_prop_, placeholder_, placeholder_2_
- ):
+ def cloneable_fixture(self, request, placeholders_prop_, placeholder_, placeholder_2_):
ph_types, expected_indices = request.param
slide_layout = SlideLayout(None, None)
placeholder_.element.ph_type = ph_types[0]
@@ -702,9 +684,7 @@ def used_by_fixture(self, request, presentation_, slide_, slide_2_):
@pytest.fixture
def LayoutPlaceholders_(self, request, placeholders_):
- return class_mock(
- request, "pptx.slide.LayoutPlaceholders", return_value=placeholders_
- )
+ return class_mock(request, "pptx.slide.LayoutPlaceholders", return_value=placeholders_)
@pytest.fixture
def LayoutShapes_(self, request, shapes_):
@@ -716,9 +696,7 @@ def package_(self, request):
@pytest.fixture
def part_prop_(self, request, slide_layout_part_):
- return property_mock(
- request, SlideLayout, "part", return_value=slide_layout_part_
- )
+ return property_mock(request, SlideLayout, "part", return_value=slide_layout_part_)
@pytest.fixture
def placeholder_(self, request):
@@ -734,9 +712,7 @@ def placeholders_(self, request):
@pytest.fixture
def placeholders_prop_(self, request, placeholders_):
- return property_mock(
- request, SlideLayout, "placeholders", return_value=placeholders_
- )
+ return property_mock(request, SlideLayout, "placeholders", return_value=placeholders_)
@pytest.fixture
def presentation_(self, request):
@@ -775,9 +751,7 @@ def it_supports_len(self, len_fixture):
assert len(slide_layouts) == expected_value
def it_can_iterate_its_slide_layouts(self, part_prop_, slide_master_part_):
- sldLayoutIdLst = element(
- "p:sldLayoutIdLst/(p:sldLayoutId{r:id=a},p:sldLayoutId{r:id=b})"
- )
+ sldLayoutIdLst = element("p:sldLayoutIdLst/(p:sldLayoutId{r:id=a},p:sldLayoutId{r:id=b})")
_slide_layouts = [
SlideLayout(element("p:sldLayout"), None),
SlideLayout(element("p:sldLayout"), None),
@@ -795,9 +769,7 @@ def it_can_iterate_its_slide_layouts(self, part_prop_, slide_master_part_):
def it_supports_indexed_access(self, slide_layout_, part_prop_, slide_master_part_):
part_prop_.return_value = slide_master_part_
slide_master_part_.related_slide_layout.return_value = slide_layout_
- slide_layouts = SlideLayouts(
- element("p:sldLayoutIdLst/p:sldLayoutId{r:id=rId1}"), None
- )
+ slide_layouts = SlideLayouts(element("p:sldLayoutIdLst/p:sldLayoutId{r:id=rId1}"), None)
slide_layout = slide_layouts[0]
@@ -805,15 +777,11 @@ def it_supports_indexed_access(self, slide_layout_, part_prop_, slide_master_par
assert slide_layout is slide_layout_
def but_it_raises_on_index_out_of_range(self, part_prop_):
- slide_layouts = SlideLayouts(
- element("p:sldLayoutIdLst/p:sldLayoutId{r:id=rId1}"), None
- )
+ slide_layouts = SlideLayouts(element("p:sldLayoutIdLst/p:sldLayoutId{r:id=rId1}"), None)
with pytest.raises(IndexError):
slide_layouts[1]
- def it_can_find_a_slide_layout_by_name(
- self, _iter_, slide_layout_, slide_layout_2_
- ):
+ def it_can_find_a_slide_layout_by_name(self, _iter_, slide_layout_, slide_layout_2_):
_iter_.return_value = iter((slide_layout_, slide_layout_2_))
slide_layout_2_.name = "pick me!"
slide_layouts = SlideLayouts(None, None)
@@ -871,14 +839,10 @@ def it_can_remove_an_unused_slide_layout(
slide_layouts.remove(slide_layout_)
- assert slide_layouts._sldLayoutIdLst.xml == xml(
- "p:sldLayoutIdLst/p:sldLayoutId{r:id=rId2}"
- )
+ assert slide_layouts._sldLayoutIdLst.xml == xml("p:sldLayoutIdLst/p:sldLayoutId{r:id=rId2}")
slide_master_part_.drop_rel.assert_called_once_with("rId1")
- def but_it_raises_on_attempt_to_remove_slide_layout_in_use(
- self, slide_layout_, slide_
- ):
+ def but_it_raises_on_attempt_to_remove_slide_layout_in_use(self, slide_layout_, slide_):
slide_layout_.used_by_slides = (slide_,)
slide_layouts = SlideLayouts(None, None)
@@ -964,9 +928,7 @@ def subclass_fixture(self):
@pytest.fixture
def SlideLayouts_(self, request, slide_layouts_):
- return class_mock(
- request, "pptx.slide.SlideLayouts", return_value=slide_layouts_
- )
+ return class_mock(request, "pptx.slide.SlideLayouts", return_value=slide_layouts_)
@pytest.fixture
def slide_layouts_(self, request):
@@ -1001,9 +963,7 @@ def it_raises_on_index_out_of_range(self, getitem_raises_fixture):
@pytest.fixture
def getitem_fixture(self, part_, slide_master_, part_prop_):
- slide_masters = SlideMasters(
- element("p:sldMasterIdLst/p:sldMasterId{r:id=rId1}"), None
- )
+ slide_masters = SlideMasters(element("p:sldMasterIdLst/p:sldMasterId{r:id=rId1}"), None)
part_.related_slide_master.return_value = slide_master_
return slide_masters, part_, slide_master_, "rId1"
@@ -1013,9 +973,7 @@ def getitem_raises_fixture(self, part_prop_):
@pytest.fixture
def iter_fixture(self, part_prop_):
- sldMasterIdLst = element(
- "p:sldMasterIdLst/(p:sldMasterId{r:id=a},p:sldMasterId{r:id=b})"
- )
+ sldMasterIdLst = element("p:sldMasterIdLst/(p:sldMasterId{r:id=a},p:sldMasterId{r:id=b})")
slide_masters = SlideMasters(sldMasterIdLst, None)
related_slide_master_ = part_prop_.return_value.related_slide_master
calls = [call("a"), call("b")]
diff --git a/tests/test_table.py b/tests/test_table.py
index 1207ff275..c53f1261f 100644
--- a/tests/test_table.py
+++ b/tests/test_table.py
@@ -1,7 +1,9 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.table` module."""
+from __future__ import annotations
+
import pytest
from pptx.dml.fill import FillFormat
@@ -10,13 +12,13 @@
from pptx.oxml.table import CT_Table, CT_TableCell, TcRange
from pptx.shapes.graphfrm import GraphicFrame
from pptx.table import (
+ Table,
_Cell,
_CellCollection,
_Column,
_ColumnCollection,
_Row,
_RowCollection,
- Table,
)
from pptx.text.text import TextFrame
from pptx.util import Inches, Length, Pt
@@ -68,9 +70,7 @@ def it_can_iterate_its_grid_cells(self, request, _Cell_):
def it_provides_access_to_its_rows(self, request):
rows_ = instance_mock(request, _RowCollection)
- _RowCollection_ = class_mock(
- request, "pptx.table._RowCollection", return_value=rows_
- )
+ _RowCollection_ = class_mock(request, "pptx.table._RowCollection", return_value=rows_)
tbl = element("a:tbl")
table = Table(tbl, None)
@@ -237,9 +237,7 @@ def it_can_change_its_margin_settings(self, margin_set_fixture):
setattr(cell, margin_prop_name, new_value)
assert cell._tc.xml == expected_xml
- def it_raises_on_margin_assigned_other_than_int_or_None(
- self, margin_raises_fixture
- ):
+ def it_raises_on_margin_assigned_other_than_int_or_None(self, margin_raises_fixture):
cell, margin_attr_name, val_of_invalid_type = margin_raises_fixture
with pytest.raises(TypeError):
setattr(cell, margin_attr_name, val_of_invalid_type)
@@ -381,9 +379,7 @@ def anchor_set_fixture(self, request):
def fill_fixture(self, cell):
return cell
- @pytest.fixture(
- params=[("a:tc", 1), ("a:tc{gridSpan=2}", 1), ("a:tc{rowSpan=42}", 42)]
- )
+ @pytest.fixture(params=[("a:tc", 1), ("a:tc{gridSpan=2}", 1), ("a:tc{rowSpan=42}", 42)])
def height_fixture(self, request):
tc_cxml, expected_value = request.param
tc = element(tc_cxml)
@@ -422,9 +418,7 @@ def margin_set_fixture(self, request):
expected_xml = xml(expected_tc_cxml)
return cell, margin_prop_name, new_value, expected_xml
- @pytest.fixture(
- params=["margin_left", "margin_right", "margin_top", "margin_bottom"]
- )
+ @pytest.fixture(params=["margin_left", "margin_right", "margin_top", "margin_bottom"])
def margin_raises_fixture(self, request):
margin_prop_name = request.param
cell = _Cell(element("a:tc"), None)
@@ -489,9 +483,7 @@ def split_fixture(self, request):
range_tcs = tuple(tcs[idx] for idx in range_tc_idxs)
return origin_tc, range_tcs
- @pytest.fixture(
- params=[("a:tc", 1), ("a:tc{rowSpan=2}", 1), ("a:tc{gridSpan=24}", 24)]
- )
+ @pytest.fixture(params=[("a:tc", 1), ("a:tc{rowSpan=2}", 1), ("a:tc{gridSpan=24}", 24)])
def width_fixture(self, request):
tc_cxml, expected_value = request.param
tc = element(tc_cxml)
@@ -561,8 +553,7 @@ def iter_fixture(self, request, _Cell_):
cell_collection = _CellCollection(tr, None)
expected_cells = [
- instance_mock(request, _Cell, name="cell%d" % idx)
- for idx in range(len(tcs))
+ instance_mock(request, _Cell, name="cell%d" % idx) for idx in range(len(tcs))
]
_Cell_.side_effect = expected_cells
calls = [call(tc, cell_collection) for tc in tcs]
@@ -601,9 +592,7 @@ def it_can_change_its_width(self, width_set_fixture):
# fixtures -------------------------------------------------------
- @pytest.fixture(
- params=[("a:gridCol{w=914400}", Inches(1)), ("a:gridCol{w=10pt}", Pt(10))]
- )
+ @pytest.fixture(params=[("a:gridCol{w=914400}", Inches(1)), ("a:gridCol{w=10pt}", Pt(10))])
def width_get_fixture(self, request):
gridCol_cxml, expected_value = request.param
column = _Column(element(gridCol_cxml), None)
diff --git a/tests/test_util.py b/tests/test_util.py
index 4944d33f4..97e46fa4c 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -1,21 +1,10 @@
-# encoding: utf-8
+"""Unit-test suite for `pptx.util` module."""
-"""
-Test suite for pptx.util module.
-"""
-
-from __future__ import absolute_import
+from __future__ import annotations
import pytest
-from pptx.compat import to_unicode
-from pptx.util import Length, Centipoints, Cm, Emu, Inches, Mm, Pt
-
-
-def test_to_unicode_raises_on_non_string():
- """to_unicode(text) raises on *text* not a string"""
- with pytest.raises(TypeError):
- to_unicode(999)
+from pptx.util import Centipoints, Cm, Emu, Inches, Length, Mm, Pt
class DescribeLength(object):
diff --git a/tests/text/test_fonts.py b/tests/text/test_fonts.py
index 275052235..995c78dd2 100644
--- a/tests/text/test_fonts.py
+++ b/tests/text/test_fonts.py
@@ -1,19 +1,18 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.text.fonts` module."""
-from __future__ import unicode_literals
+from __future__ import annotations
import io
-import pytest
-
from struct import calcsize
-from pptx.compat import BytesIO
+import pytest
+
from pptx.text.fonts import (
+ FontFiles,
_BaseTable,
_Font,
- FontFiles,
_HeadTable,
_NameTable,
_Stream,
@@ -85,9 +84,7 @@ def find_fixture(self, request, _installed_fonts_):
return family_name, is_bold, is_italic, expected_path
@pytest.fixture(params=[("darwin", ["a", "b"]), ("win32", ["c", "d"])])
- def font_dirs_fixture(
- self, request, _os_x_font_directories_, _windows_font_directories_
- ):
+ def font_dirs_fixture(self, request, _os_x_font_directories_, _windows_font_directories_):
platform, expected_dirs = request.param
dirs_meth_mock = {
"darwin": _os_x_font_directories_,
@@ -172,9 +169,7 @@ def _os_x_font_directories_(self, request):
@pytest.fixture
def _windows_font_directories_(self, request):
- return method_mock(
- request, FontFiles, "_windows_font_directories", autospec=False
- )
+ return method_mock(request, FontFiles, "_windows_font_directories", autospec=False)
class Describe_Font(object):
@@ -227,9 +222,7 @@ def it_reads_the_header_to_help_read_font(self, request):
# fixtures ---------------------------------------------
- @pytest.fixture(
- params=[("head", True, True), ("head", False, False), ("foob", True, False)]
- )
+ @pytest.fixture(params=[("head", True, True), ("head", False, False), ("foob", True, False)])
def bold_fixture(self, request, _tables_, head_table_):
key, is_bold, expected_value = request.param
head_table_.is_bold = is_bold
@@ -245,9 +238,7 @@ def family_fixture(self, _tables_, name_table_):
name_table_.family_name = expected_name
return font, expected_name
- @pytest.fixture(
- params=[("head", True, True), ("head", False, False), ("foob", True, False)]
- )
+ @pytest.fixture(params=[("head", True, True), ("head", False, False), ("foob", True, False)])
def italic_fixture(self, request, _tables_, head_table_):
key, is_italic, expected_value = request.param
head_table_.is_italic = is_italic
@@ -468,7 +459,7 @@ def italic_fixture(self, request, _macStyle_):
@pytest.fixture
def macStyle_fixture(self):
bytes_ = b"xxxxyyyy....................................\xF0\xBA........"
- stream = _Stream(BytesIO(bytes_))
+ stream = _Stream(io.BytesIO(bytes_))
offset, length = 0, len(bytes_)
head_table = _HeadTable(None, stream, offset, length)
expected_value = 61626
@@ -503,9 +494,7 @@ def it_provides_access_to_its_names_to_help_props(self, request):
_iter_names_.assert_called_once_with(name_table)
assert names == {(0, 1): "Foobar", (3, 1): "Barfoo"}
- def it_iterates_over_its_names_to_help_read_names(
- self, request, _table_bytes_prop_
- ):
+ def it_iterates_over_its_names_to_help_read_names(self, request, _table_bytes_prop_):
property_mock(request, _NameTable, "_table_header", return_value=(0, 3, 42))
_table_bytes_prop_.return_value = "xXx"
_read_name_ = method_mock(
@@ -533,9 +522,7 @@ def it_reads_the_table_header_to_help_read_names(self, header_fixture):
def it_buffers_the_table_bytes_to_help_read_names(self, bytes_fixture):
name_table, expected_value = bytes_fixture
table_bytes = name_table._table_bytes
- name_table._stream.read.assert_called_once_with(
- name_table._offset, name_table._length
- )
+ name_table._stream.read.assert_called_once_with(name_table._offset, name_table._length)
assert table_bytes == expected_value
def it_reads_a_name_to_help_read_names(self, request):
@@ -555,9 +542,7 @@ def it_reads_a_name_to_help_read_names(self, request):
name_str_offset,
),
)
- _read_name_text_ = method_mock(
- request, _NameTable, "_read_name_text", return_value=name
- )
+ _read_name_text_ = method_mock(request, _NameTable, "_read_name_text", return_value=name)
name_table = _NameTable(None, None, None, None)
actual = name_table._read_name(bufr, idx, strs_offset)
@@ -591,9 +576,7 @@ def it_reads_name_text_to_help_read_names(self, name_text_fixture):
name_table._raw_name_string.assert_called_once_with(
bufr, strings_offset, name_str_offset, length
)
- name_table._decode_name.assert_called_once_with(
- raw_name, platform_id, encoding_id
- )
+ name_table._decode_name.assert_called_once_with(raw_name, platform_id, encoding_id)
assert name is name_
def it_reads_name_bytes_to_help_read_names(self, raw_fixture):
diff --git a/tests/text/test_layout.py b/tests/text/test_layout.py
index 2627660f2..6e2c83d6a 100644
--- a/tests/text/test_layout.py
+++ b/tests/text/test_layout.py
@@ -1,10 +1,12 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.text.layout` module."""
+from __future__ import annotations
+
import pytest
-from pptx.text.layout import _BinarySearchTree, _Line, _LineSource, TextFitter
+from pptx.text.layout import TextFitter, _BinarySearchTree, _Line, _LineSource
from ..unitutil.mock import (
ANY,
@@ -31,9 +33,7 @@ def it_can_determine_the_best_fit_font_size(self, request, line_source_):
)
extents, max_size = (19, 20), 42
- font_size = TextFitter.best_fit_font_size(
- "Foobar", extents, max_size, "foobar.ttf"
- )
+ font_size = TextFitter.best_fit_font_size("Foobar", extents, max_size, "foobar.ttf")
_LineSource_.assert_called_once_with("Foobar")
_init_.assert_called_once_with(line_source_, extents, "foobar.ttf")
@@ -46,9 +46,7 @@ def it_finds_best_fit_font_size_to_help_best_fit(self, _best_fit_fixture):
font_size = text_fitter._best_fit_font_size(max_size)
- _BinarySearchTree_.from_ordered_sequence.assert_called_once_with(
- range(1, max_size + 1)
- )
+ _BinarySearchTree_.from_ordered_sequence.assert_called_once_with(range(1, max_size + 1))
sizes_.find_max.assert_called_once_with(predicate_)
assert font_size is font_size_
@@ -70,9 +68,7 @@ def it_provides_a_fits_inside_predicate_fn(
text_lines,
expected_value,
):
- _wrap_lines_ = method_mock(
- request, TextFitter, "_wrap_lines", return_value=text_lines
- )
+ _wrap_lines_ = method_mock(request, TextFitter, "_wrap_lines", return_value=text_lines)
_rendered_size_.return_value = (None, 50)
text_fitter = TextFitter(line_source_, extents, "foobar.ttf")
@@ -80,9 +76,7 @@ def it_provides_a_fits_inside_predicate_fn(
result = predicate(point_size)
_wrap_lines_.assert_called_once_with(text_fitter, line_source_, point_size)
- _rendered_size_.assert_called_once_with(
- "Ty", point_size, text_fitter._font_file
- )
+ _rendered_size_.assert_called_once_with("Ty", point_size, text_fitter._font_file)
assert result is expected_value
def it_provides_a_fits_in_width_predicate_fn(self, fits_cx_pred_fixture):
@@ -92,9 +86,7 @@ def it_provides_a_fits_in_width_predicate_fn(self, fits_cx_pred_fixture):
predicate = text_fitter._fits_in_width_predicate(point_size)
result = predicate(line)
- _rendered_size_.assert_called_once_with(
- line.text, point_size, text_fitter._font_file
- )
+ _rendered_size_.assert_called_once_with(line.text, point_size, text_fitter._font_file)
assert result is expected_value
def it_wraps_lines_to_help_best_fit(self, request):
@@ -114,13 +106,9 @@ def it_wraps_lines_to_help_best_fit(self, request):
call(text_fitter, remainder, 21),
]
- def it_breaks_off_a_line_to_help_wrap(
- self, request, line_source_, _BinarySearchTree_
- ):
+ def it_breaks_off_a_line_to_help_wrap(self, request, line_source_, _BinarySearchTree_):
bst_ = instance_mock(request, _BinarySearchTree)
- _fits_in_width_predicate_ = method_mock(
- request, TextFitter, "_fits_in_width_predicate"
- )
+ _fits_in_width_predicate_ = method_mock(request, TextFitter, "_fits_in_width_predicate")
_BinarySearchTree_.from_ordered_sequence.return_value = bst_
predicate_ = _fits_in_width_predicate_.return_value
max_value_ = bst_.find_max.return_value
diff --git a/tests/text/test_text.py b/tests/text/test_text.py
index 28f0e65a6..3a1a7a0bb 100644
--- a/tests/text/test_text.py
+++ b/tests/text/test_text.py
@@ -1,20 +1,21 @@
-# encoding: utf-8
+# pyright: reportPrivateUsage=false
"""Unit-test suite for `pptx.text.text` module."""
-from __future__ import unicode_literals
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, cast
import pytest
-from pptx.compat import is_unicode
from pptx.dml.color import ColorFormat
from pptx.dml.fill import FillFormat
from pptx.enum.lang import MSO_LANGUAGE_ID
from pptx.enum.text import MSO_ANCHOR, MSO_AUTO_SIZE, MSO_UNDERLINE, PP_ALIGN
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
-from pptx.opc.package import Part
+from pptx.opc.package import XmlPart
from pptx.shapes.autoshape import Shape
-from pptx.text.text import Font, _Hyperlink, _Paragraph, _Run, TextFrame
+from pptx.text.text import Font, TextFrame, _Hyperlink, _Paragraph, _Run
from pptx.util import Inches, Pt
from ..oxml.unitdata.text import a_p, a_t, an_hlinkClick, an_r, an_rPr
@@ -27,6 +28,9 @@
property_mock,
)
+if TYPE_CHECKING:
+ from pptx.oxml.text import CT_TextBody, CT_TextParagraph
+
class DescribeTextFrame(object):
"""Unit-test suite for `pptx.text.text.TextFrame` object."""
@@ -40,10 +44,29 @@ def it_knows_its_autosize_setting(self, autosize_get_fixture):
text_frame, expected_value = autosize_get_fixture
assert text_frame.auto_size == expected_value
- def it_can_change_its_autosize_setting(self, autosize_set_fixture):
- text_frame, value, expected_xml = autosize_set_fixture
+ @pytest.mark.parametrize(
+ ("txBody_cxml", "value", "expected_cxml"),
+ [
+ ("p:txBody/a:bodyPr", MSO_AUTO_SIZE.NONE, "p:txBody/a:bodyPr/a:noAutofit"),
+ (
+ "p:txBody/a:bodyPr/a:noAutofit",
+ MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT,
+ "p:txBody/a:bodyPr/a:spAutoFit",
+ ),
+ (
+ "p:txBody/a:bodyPr/a:spAutoFit",
+ MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE,
+ "p:txBody/a:bodyPr/a:normAutofit",
+ ),
+ ("p:txBody/a:bodyPr/a:normAutofit", None, "p:txBody/a:bodyPr"),
+ ],
+ )
+ def it_can_change_its_autosize_setting(
+ self, txBody_cxml: str, value: MSO_AUTO_SIZE | None, expected_cxml: str
+ ):
+ text_frame = TextFrame(element(txBody_cxml), None)
text_frame.auto_size = value
- assert text_frame._txBody.xml == expected_xml
+ assert text_frame._txBody.xml == xml(expected_cxml)
@pytest.mark.parametrize(
"txBody_cxml",
@@ -69,14 +92,41 @@ def it_can_change_its_margin_settings(self, margin_set_fixture):
setattr(text_frame, prop_name, new_value)
assert text_frame._txBody.xml == expected_xml
- def it_knows_its_vertical_alignment(self, anchor_get_fixture):
- text_frame, expected_value = anchor_get_fixture
+ @pytest.mark.parametrize(
+ ("txBody_cxml", "expected_value"),
+ [
+ ("p:txBody/a:bodyPr", None),
+ ("p:txBody/a:bodyPr{anchor=t}", MSO_ANCHOR.TOP),
+ ("p:txBody/a:bodyPr{anchor=b}", MSO_ANCHOR.BOTTOM),
+ ],
+ )
+ def it_knows_its_vertical_alignment(self, txBody_cxml: str, expected_value: MSO_ANCHOR | None):
+ text_frame = TextFrame(cast("CT_TextBody", element(txBody_cxml)), None)
assert text_frame.vertical_anchor == expected_value
- def it_can_change_its_vertical_alignment(self, anchor_set_fixture):
- text_frame, new_value, expected_xml = anchor_set_fixture
+ @pytest.mark.parametrize(
+ ("txBody_cxml", "new_value", "expected_cxml"),
+ [
+ ("p:txBody/a:bodyPr", MSO_ANCHOR.TOP, "p:txBody/a:bodyPr{anchor=t}"),
+ (
+ "p:txBody/a:bodyPr{anchor=t}",
+ MSO_ANCHOR.MIDDLE,
+ "p:txBody/a:bodyPr{anchor=ctr}",
+ ),
+ (
+ "p:txBody/a:bodyPr{anchor=ctr}",
+ MSO_ANCHOR.BOTTOM,
+ "p:txBody/a:bodyPr{anchor=b}",
+ ),
+ ("p:txBody/a:bodyPr{anchor=b}", None, "p:txBody/a:bodyPr"),
+ ],
+ )
+ def it_can_change_its_vertical_alignment(
+ self, txBody_cxml: str, new_value: MSO_ANCHOR | None, expected_cxml: str
+ ):
+ text_frame = TextFrame(cast("CT_TextBody", element(txBody_cxml)), None)
text_frame.vertical_anchor = new_value
- assert text_frame._element.xml == expected_xml
+ assert text_frame._element.xml == xml(expected_cxml)
def it_knows_its_word_wrap_setting(self, wrap_get_fixture):
text_frame, expected_value = wrap_get_fixture
@@ -105,9 +155,7 @@ def it_knows_the_part_it_belongs_to(self, text_frame_with_parent_):
part = text_frame.part
assert part is parent_.part
- def it_knows_what_text_it_contains(
- self, request, text_get_fixture, paragraphs_prop_
- ):
+ def it_knows_what_text_it_contains(self, request, text_get_fixture, paragraphs_prop_):
paragraph_texts, expected_value = text_get_fixture
paragraphs_prop_.return_value = tuple(
instance_mock(request, _Paragraph, text=text) for text in paragraph_texts
@@ -157,9 +205,7 @@ def it_calculates_its_best_fit_font_size_to_help_fit_text(self, size_font_fixtur
font_size = text_frame._best_fit_font_size(family, max_size, bold, italic, None)
FontFiles_.find.assert_called_once_with(family, bold, italic)
- TextFitter_.best_fit_font_size.assert_called_once_with(
- text, extents, max_size, font_file_
- )
+ TextFitter_.best_fit_font_size.assert_called_once_with(text, extents, max_size, font_file_)
assert font_size is font_size_
def it_calculates_its_effective_size_to_help_fit_text(self):
@@ -200,40 +246,6 @@ def add_paragraph_fixture(self, request):
expected_xml = xml(expected_cxml)
return text_frame, expected_xml
- @pytest.fixture(
- params=[
- ("p:txBody/a:bodyPr", None),
- ("p:txBody/a:bodyPr{anchor=t}", MSO_ANCHOR.TOP),
- ("p:txBody/a:bodyPr{anchor=b}", MSO_ANCHOR.BOTTOM),
- ]
- )
- def anchor_get_fixture(self, request):
- txBody_cxml, expected_value = request.param
- text_frame = TextFrame(element(txBody_cxml), None)
- return text_frame, expected_value
-
- @pytest.fixture(
- params=[
- ("p:txBody/a:bodyPr", MSO_ANCHOR.TOP, "p:txBody/a:bodyPr{anchor=t}"),
- (
- "p:txBody/a:bodyPr{anchor=t}",
- MSO_ANCHOR.MIDDLE,
- "p:txBody/a:bodyPr{anchor=ctr}",
- ),
- (
- "p:txBody/a:bodyPr{anchor=ctr}",
- MSO_ANCHOR.BOTTOM,
- "p:txBody/a:bodyPr{anchor=b}",
- ),
- ("p:txBody/a:bodyPr{anchor=b}", None, "p:txBody/a:bodyPr"),
- ]
- )
- def anchor_set_fixture(self, request):
- txBody_cxml, new_value, expected_cxml = request.param
- text_frame = TextFrame(element(txBody_cxml), None)
- expected_xml = xml(expected_cxml)
- return text_frame, new_value, expected_xml
-
@pytest.fixture(
params=[
("p:txBody/a:bodyPr", None),
@@ -247,28 +259,6 @@ def autosize_get_fixture(self, request):
text_frame = TextFrame(element(txBody_cxml), None)
return text_frame, expected_value
- @pytest.fixture(
- params=[
- ("p:txBody/a:bodyPr", MSO_AUTO_SIZE.NONE, "p:txBody/a:bodyPr/a:noAutofit"),
- (
- "p:txBody/a:bodyPr/a:noAutofit",
- MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT,
- "p:txBody/a:bodyPr/a:spAutoFit",
- ),
- (
- "p:txBody/a:bodyPr/a:spAutoFit",
- MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE,
- "p:txBody/a:bodyPr/a:normAutofit",
- ),
- ("p:txBody/a:bodyPr/a:normAutofit", None, "p:txBody/a:bodyPr"),
- ]
- )
- def autosize_set_fixture(self, request):
- txBody_cxml, value, expected_cxml = request.param
- text_frame = TextFrame(element(txBody_cxml), None)
- expected_xml = xml(expected_cxml)
- return text_frame, value, expected_xml
-
@pytest.fixture(
params=[
("p:txBody/a:bodyPr", "left", "emu", Inches(0.1)),
@@ -389,9 +379,7 @@ def size_font_fixture(self, FontFiles_, TextFitter_, text_prop_, _extents_prop_)
font_size,
)
- @pytest.fixture(
- params=[(["foobar"], "foobar"), (["foo", "bar", "baz"], "foo\nbar\nbaz")]
- )
+ @pytest.fixture(params=[(["foobar"], "foobar"), (["foo", "bar", "baz"], "foo\nbar\nbaz")])
def text_get_fixture(self, request):
paragraph_texts, expected_value = request.param
return paragraph_texts, expected_value
@@ -545,9 +533,7 @@ def it_provides_access_to_its_fill(self, font):
# fixtures ---------------------------------------------
- @pytest.fixture(
- params=[("a:rPr", None), ("a:rPr{b=0}", False), ("a:rPr{b=1}", True)]
- )
+ @pytest.fixture(params=[("a:rPr", None), ("a:rPr{b=0}", False), ("a:rPr{b=1}", True)])
def bold_get_fixture(self, request):
rPr_cxml, expected_value = request.param
font = Font(element(rPr_cxml))
@@ -566,9 +552,7 @@ def bold_set_fixture(self, request):
expected_xml = xml(expected_rPr_cxml)
return font, new_value, expected_xml
- @pytest.fixture(
- params=[("a:rPr", None), ("a:rPr{i=0}", False), ("a:rPr{i=1}", True)]
- )
+ @pytest.fixture(params=[("a:rPr", None), ("a:rPr{i=0}", False), ("a:rPr{i=1}", True)])
def italic_get_fixture(self, request):
rPr_cxml, expected_value = request.param
font = Font(element(rPr_cxml))
@@ -616,9 +600,7 @@ def language_id_set_fixture(self, request):
expected_xml = xml(expected_rPr_cxml)
return font, new_value, expected_xml
- @pytest.fixture(
- params=[("a:rPr", None), ("a:rPr/a:latin{typeface=Foobar}", "Foobar")]
- )
+ @pytest.fixture(params=[("a:rPr", None), ("a:rPr/a:latin{typeface=Foobar}", "Foobar")])
def name_get_fixture(self, request):
rPr_cxml, expected_value = request.param
font = Font(element(rPr_cxml))
@@ -647,9 +629,7 @@ def size_get_fixture(self, request):
font = Font(element(rPr_cxml))
return font, expected_value
- @pytest.fixture(
- params=[("a:rPr", Pt(24), "a:rPr{sz=2400}"), ("a:rPr{sz=2400}", None, "a:rPr")]
- )
+ @pytest.fixture(params=[("a:rPr", Pt(24), "a:rPr{sz=2400}"), ("a:rPr{sz=2400}", None, "a:rPr")])
def size_set_fixture(self, request):
rPr_cxml, new_value, expected_rPr_cxml = request.param
font = Font(element(rPr_cxml))
@@ -705,9 +685,7 @@ def it_has_None_for_address_when_no_hyperlink_is_present(self, hlink):
def it_can_set_the_target_url(self, hlink, rPr_with_hlinkClick_xml, url):
hlink.address = url
# verify -----------------------
- hlink.part.relate_to.assert_called_once_with(
- url, RT.HYPERLINK, is_external=True
- )
+ hlink.part.relate_to.assert_called_once_with(url, RT.HYPERLINK, is_external=True)
assert hlink._rPr.xml == rPr_with_hlinkClick_xml
assert hlink.address == url
@@ -717,9 +695,7 @@ def it_can_remove_the_hyperlink(self, remove_hlink_fixture_):
assert hlink._rPr.xml == rPr_xml
hlink.part.drop_rel.assert_called_once_with(rId)
- def it_should_remove_the_hyperlink_when_url_set_to_empty_string(
- self, remove_hlink_fixture_
- ):
+ def it_should_remove_the_hyperlink_when_url_set_to_empty_string(self, remove_hlink_fixture_):
hlink, rPr_xml, rId = remove_hlink_fixture_
hlink.address = ""
assert hlink._rPr.xml == rPr_xml
@@ -733,16 +709,12 @@ def it_can_change_the_target_url(self, change_hlink_fixture_):
# verify -----------------------
assert hlink._rPr.xml == new_rPr_xml
hlink.part.drop_rel.assert_called_once_with(rId_existing)
- hlink.part.relate_to.assert_called_once_with(
- new_url, RT.HYPERLINK, is_external=True
- )
+ hlink.part.relate_to.assert_called_once_with(new_url, RT.HYPERLINK, is_external=True)
# fixtures ---------------------------------------------
@pytest.fixture
- def change_hlink_fixture_(
- self, request, hlink_with_hlinkClick, rId, rId_2, part_, url_2
- ):
+ def change_hlink_fixture_(self, request, hlink_with_hlinkClick, rId, rId_2, part_, url_2):
hlinkClick_bldr = an_hlinkClick().with_rId(rId_2)
new_rPr_xml = an_rPr().with_nsdecls("a", "r").with_child(hlinkClick_bldr).xml()
part_.relate_to.return_value = rId_2
@@ -772,7 +744,7 @@ def part_(self, request, url, rId):
Mock Part instance suitable for patching into _Hyperlink.part
property. It returns url for target_ref() and rId for relate_to().
"""
- part_ = instance_mock(request, Part)
+ part_ = instance_mock(request, XmlPart)
part_.target_ref.return_value = url
part_.relate_to.return_value = rId
return part_
@@ -896,15 +868,34 @@ def it_knows_what_text_it_contains(self, text_get_fixture):
text = paragraph.text
assert text == expected_value
- assert is_unicode(text)
+ assert isinstance(text, str)
- def it_can_change_its_text(self, text_set_fixture):
- p, value, expected_xml = text_set_fixture
+ @pytest.mark.parametrize(
+ ("p_cxml", "value", "expected_cxml"),
+ [
+ ('a:p/(a:r/a:t"foo",a:r/a:t"bar")', "foobar", 'a:p/a:r/a:t"foobar"'),
+ ("a:p", "", "a:p"),
+ ("a:p", "foobar", 'a:p/a:r/a:t"foobar"'),
+ ("a:p", "foo\nbar", 'a:p/(a:r/a:t"foo",a:br,a:r/a:t"bar")'),
+ ("a:p", "\vfoo\n", 'a:p/(a:br,a:r/a:t"foo",a:br)'),
+ ("a:p", "\n\nfoo", 'a:p/(a:br,a:br,a:r/a:t"foo")'),
+ ("a:p", "foo\n", 'a:p/(a:r/a:t"foo",a:br)'),
+ ("a:p", "foo\x07\n", 'a:p/(a:r/a:t"foo_x0007_",a:br)'),
+ ("a:p", "ŮŦƑ-8\x1bliteral", 'a:p/a:r/a:t"ŮŦƑ-8_x001B_literal"'),
+ (
+ "a:p",
+ "utf-8 unicode: Hér er texti",
+ 'a:p/a:r/a:t"utf-8 unicode: Hér er texti"',
+ ),
+ ],
+ )
+ def it_can_change_its_text(self, p_cxml: str, value: str, expected_cxml: str):
+ p = cast("CT_TextParagraph", element(p_cxml))
paragraph = _Paragraph(p, None)
paragraph.text = value
- assert paragraph._element.xml == expected_xml
+ assert paragraph._element.xml == xml(expected_cxml)
# fixtures ---------------------------------------------
@@ -1128,32 +1119,6 @@ def text_get_fixture(self, request):
p = element(p_cxml)
return p, expected_value
- @pytest.fixture(
- params=[
- ('a:p/(a:r/a:t"foo",a:r/a:t"bar")', "foobar", 'a:p/a:r/a:t"foobar"'),
- ("a:p", "", "a:p"),
- ("a:p", "foobar", 'a:p/a:r/a:t"foobar"'),
- ("a:p", "foo\nbar", 'a:p/(a:r/a:t"foo",a:br,a:r/a:t"bar")'),
- ("a:p", "\vfoo\n", 'a:p/(a:br,a:r/a:t"foo",a:br)'),
- ("a:p", "\n\nfoo", 'a:p/(a:br,a:br,a:r/a:t"foo")'),
- ("a:p", "foo\n", 'a:p/(a:r/a:t"foo",a:br)'),
- ("a:p", b"foo\x07\n", 'a:p/(a:r/a:t"foo_x0007_",a:br)'),
- ("a:p", b"7-bit str", 'a:p/a:r/a:t"7-bit str"'),
- ("a:p", b"8-\xc9\x93\xc3\xaf\xc8\xb6 str", 'a:p/a:r/a:t"8-ɓïȶ str"'),
- ("a:p", "ŮŦƑ-8\x1bliteral", 'a:p/a:r/a:t"ŮŦƑ-8_x001B_literal"'),
- (
- "a:p",
- "utf-8 unicode: Hér er texti",
- 'a:p/a:r/a:t"utf-8 unicode: Hér er texti"',
- ),
- ]
- )
- def text_set_fixture(self, request):
- p_cxml, value, expected_cxml = request.param
- p = element(p_cxml)
- expected_xml = xml(expected_cxml)
- return p, value, expected_xml
-
# fixture components -----------------------------------
@pytest.fixture
@@ -1193,7 +1158,7 @@ def it_can_get_the_text_of_the_run(self, text_get_fixture):
run, expected_value = text_get_fixture
text = run.text
assert text == expected_value
- assert is_unicode(text)
+ assert isinstance(text, str)
@pytest.mark.parametrize(
"r_cxml, new_value, expected_r_cxml",
diff --git a/tests/unitdata.py b/tests/unitdata.py
index f40fcaf8c..978647865 100644
--- a/tests/unitdata.py
+++ b/tests/unitdata.py
@@ -1,10 +1,6 @@
-# encoding: utf-8
+"""Shared objects for unit data builder modules."""
-"""
-Shared objects for unit data builder modules
-"""
-
-from __future__ import absolute_import, print_function, unicode_literals
+from __future__ import annotations
from pptx.oxml import parse_xml
from pptx.oxml.ns import nsdecls
diff --git a/tests/unitutil/__init__.py b/tests/unitutil/__init__.py
index 38eca7c27..7f5a5b584 100644
--- a/tests/unitutil/__init__.py
+++ b/tests/unitutil/__init__.py
@@ -1,8 +1,6 @@
-# encoding: utf-8
+"""Helper objects for unit testing."""
-"""
-Helper objects for unit testing.
-"""
+from __future__ import annotations
def count(start=0, step=1):
diff --git a/tests/unitutil/cxml.py b/tests/unitutil/cxml.py
index d44cb51d1..79e217c20 100644
--- a/tests/unitutil/cxml.py
+++ b/tests/unitutil/cxml.py
@@ -1,48 +1,48 @@
-# encoding: utf-8
+"""Parser for Compact XML Expression Language (CXEL) ('see-ex-ell').
-"""
-Parser for Compact XML Expression Language (CXEL) ('see-ex-ell'), a compact
-XML specification language I made up that's useful for producing XML element
+CXEL is a compact XML specification language I made up that's useful for producing XML element
trees suitable for unit testing.
"""
-from __future__ import print_function
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
from pyparsing import (
- alphas,
- alphanums,
Combine,
- dblQuotedString,
- delimitedList,
Forward,
Group,
Literal,
Optional,
- removeQuotes,
- stringEnd,
Suppress,
Word,
+ alphanums,
+ alphas,
+ dblQuotedString,
+ delimitedList,
+ removeQuotes,
+ stringEnd,
)
from pptx.oxml import parse_xml
from pptx.oxml.ns import _nsmap as nsmap
+if TYPE_CHECKING:
+ from pptx.oxml.xmlchemy import BaseOxmlElement
# ====================================================================
# api functions
# ====================================================================
-def element(cxel_str):
- """
- Return an oxml element parsed from the XML generated from *cxel_str*.
- """
+def element(cxel_str: str) -> BaseOxmlElement:
+ """Return an oxml element parsed from the XML generated from `cxel_str`."""
_xml = xml(cxel_str)
return parse_xml(_xml)
-def xml(cxel_str):
- """Return the XML generated from *cxel_str*."""
+def xml(cxel_str: str) -> str:
+ """Return the XML generated from `cxel_str`."""
root_node.parseWithTabs()
root_token = root_node.parseString(cxel_str)
xml = root_token.element.xml
@@ -274,9 +274,7 @@ def grammar():
child_node_list << (open_paren + delimitedList(node) + close_paren | node)
root_node = (
- element("element")
- + Group(Optional(slash + child_node_list))("child_node_list")
- + stringEnd
+ element("element") + Group(Optional(slash + child_node_list))("child_node_list") + stringEnd
).setParseAction(connect_root_node_children)
return root_node
diff --git a/tests/unitutil/file.py b/tests/unitutil/file.py
index 0d25aac5a..938d42ef0 100644
--- a/tests/unitutil/file.py
+++ b/tests/unitutil/file.py
@@ -1,29 +1,24 @@
-# encoding: utf-8
-
"""Utility functions for loading files for unit testing."""
import os
import sys
-
_thisdir = os.path.split(__file__)[0]
test_file_dir = os.path.abspath(os.path.join(_thisdir, "..", "test_files"))
-def absjoin(*paths):
+def absjoin(*paths: str):
return os.path.abspath(os.path.join(*paths))
-def snippet_bytes(snippet_file_name):
+def snippet_bytes(snippet_file_name: str):
"""Return bytes read from snippet file having `snippet_file_name`."""
- snippet_file_path = os.path.join(
- test_file_dir, "snippets", "%s.txt" % snippet_file_name
- )
+ snippet_file_path = os.path.join(test_file_dir, "snippets", "%s.txt" % snippet_file_name)
with open(snippet_file_path, "rb") as f:
return f.read().strip()
-def snippet_seq(name, offset=0, count=sys.maxsize):
+def snippet_seq(name: str, offset: int = 0, count: int = sys.maxsize):
"""
Return a tuple containing the unicode text snippets read from the snippet
file having *name*. Snippets are delimited by a blank line. If specified,
@@ -37,27 +32,25 @@ def snippet_seq(name, offset=0, count=sys.maxsize):
return tuple(snippets[start:end])
-def snippet_text(snippet_file_name):
+def snippet_text(snippet_file_name: str):
"""
Return the unicode text read from the test snippet file having
*snippet_file_name*.
"""
- snippet_file_path = os.path.join(
- test_file_dir, "snippets", "%s.txt" % snippet_file_name
- )
+ snippet_file_path = os.path.join(test_file_dir, "snippets", "%s.txt" % snippet_file_name)
with open(snippet_file_path, "rb") as f:
snippet_bytes = f.read()
return snippet_bytes.decode("utf-8")
-def testfile(name):
+def testfile(name: str):
"""
Return the absolute path to test file having *name*.
"""
return absjoin(test_file_dir, name)
-def testfile_bytes(*segments):
+def testfile_bytes(*segments: str):
"""Return bytes of file at path formed by adding `segments` to test file dir."""
path = os.path.join(test_file_dir, *segments)
with open(path, "rb") as f:
diff --git a/tests/unitutil/mock.py b/tests/unitutil/mock.py
index 849a927cb..3b681d983 100644
--- a/tests/unitutil/mock.py
+++ b/tests/unitutil/mock.py
@@ -1,22 +1,28 @@
-# encoding: utf-8
-
"""Utility functions wrapping the excellent `mock` library."""
-from __future__ import absolute_import
+from __future__ import annotations
+
+from typing import Any
+from unittest import mock
+from unittest.mock import (
+ ANY,
+ MagicMock,
+ Mock,
+ PropertyMock,
+ call,
+ create_autospec,
+ mock_open,
+ patch,
+)
-import sys
+from pytest import FixtureRequest, LogCaptureFixture # noqa: PT013
-if sys.version_info >= (3, 3):
- from unittest import mock # noqa
- from unittest.mock import ANY, call, MagicMock # noqa
- from unittest.mock import create_autospec, Mock, mock_open, patch, PropertyMock
-else: # pragma: no cover
- import mock # noqa
- from mock import ANY, call, MagicMock # noqa
- from mock import create_autospec, Mock, mock_open, patch, PropertyMock
+__all__ = ["ANY", "FixtureRequest", "LogCaptureFixture", "MagicMock", "call", "mock"]
-def class_mock(request, q_class_name, autospec=True, **kwargs):
+def class_mock(
+ request: FixtureRequest, q_class_name: str, autospec: bool = True, **kwargs: Any
+) -> Mock:
"""Return a mock patching the class with qualified name *q_class_name*.
The mock is autospec'ed based on the patched class unless the optional argument
@@ -28,8 +34,10 @@ def class_mock(request, q_class_name, autospec=True, **kwargs):
return _patch.start()
-def cls_attr_mock(request, cls, attr_name, name=None, **kwargs): # pragma: no cover
- """Return a mock for attribute (class variable) `attr_name` on `cls`.
+def cls_attr_mock(
+ request: FixtureRequest, cls: type, attr_name: str, name: str | None = None, **kwargs: Any
+) -> Mock:
+ """Return a mock for an attribute (class variable) `attr_name` on `cls`.
Patch is reversed after pytest uses it.
"""
@@ -39,7 +47,9 @@ def cls_attr_mock(request, cls, attr_name, name=None, **kwargs): # pragma: no c
return _patch.start()
-def function_mock(request, q_function_name, autospec=True, **kwargs):
+def function_mock(
+ request: FixtureRequest, q_function_name: str, autospec: bool = True, **kwargs: Any
+):
"""Return mock patching function with qualified name `q_function_name`.
Patch is reversed after calling test returns.
@@ -49,19 +59,23 @@ def function_mock(request, q_function_name, autospec=True, **kwargs):
return _patch.start()
-def initializer_mock(request, cls, autospec=True, **kwargs):
+def initializer_mock(request: FixtureRequest, cls: type, autospec: bool = True, **kwargs: Any):
"""Return mock for __init__() method on `cls`.
The patch is reversed after pytest uses it.
"""
- _patch = patch.object(
- cls, "__init__", autospec=autospec, return_value=None, **kwargs
- )
+ _patch = patch.object(cls, "__init__", autospec=autospec, return_value=None, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()
-def instance_mock(request, cls, name=None, spec_set=True, **kwargs):
+def instance_mock(
+ request: FixtureRequest,
+ cls: type,
+ name: str | None = None,
+ spec_set: bool = True,
+ **kwargs: Any,
+) -> Mock:
"""Return mock for instance of `cls` that draws its spec from that class.
The mock does not allow new attributes to be set on the instance. If `name` is
@@ -73,7 +87,7 @@ def instance_mock(request, cls, name=None, spec_set=True, **kwargs):
return create_autospec(cls, _name=name, spec_set=spec_set, instance=True, **kwargs)
-def loose_mock(request, name=None, **kwargs):
+def loose_mock(request: FixtureRequest, name: str | None = None, **kwargs: Any):
"""Return a "loose" mock, meaning it has no spec to constrain calls on it.
Additional keyword arguments are passed through to Mock(). If called without a name,
@@ -82,7 +96,9 @@ def loose_mock(request, name=None, **kwargs):
return Mock(name=request.fixturename if name is None else name, **kwargs)
-def method_mock(request, cls, method_name, autospec=True, **kwargs):
+def method_mock(
+ request: FixtureRequest, cls: type, method_name: str, autospec: bool = True, **kwargs: Any
+):
"""Return mock for method `method_name` on `cls`.
The patch is reversed after pytest uses it.
@@ -92,7 +108,7 @@ def method_mock(request, cls, method_name, autospec=True, **kwargs):
return _patch.start()
-def open_mock(request, module_name, **kwargs):
+def open_mock(request: FixtureRequest, module_name: str, **kwargs: Any):
"""Return a mock for the builtin `open()` method in `module_name`."""
target = "%s.open" % module_name
_patch = patch(target, mock_open(), create=True, **kwargs)
@@ -100,14 +116,14 @@ def open_mock(request, module_name, **kwargs):
return _patch.start()
-def property_mock(request, cls, prop_name, **kwargs):
+def property_mock(request: FixtureRequest, cls: type, prop_name: str, **kwargs: Any):
"""Return a mock for property `prop_name` on class `cls`."""
_patch = patch.object(cls, prop_name, new_callable=PropertyMock, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()
-def var_mock(request, q_var_name, **kwargs):
+def var_mock(request: FixtureRequest, q_var_name: str, **kwargs: Any):
"""Return mock patching the variable with qualified name *q_var_name*."""
_patch = patch(q_var_name, **kwargs)
request.addfinalizer(_patch.stop)
diff --git a/typings/behave/__init__.pyi b/typings/behave/__init__.pyi
new file mode 100644
index 000000000..f8ffc2058
--- /dev/null
+++ b/typings/behave/__init__.pyi
@@ -0,0 +1,17 @@
+from __future__ import annotations
+
+from typing import Callable
+
+from typing_extensions import TypeAlias
+
+from .runner import Context
+
+_ThreeArgStep: TypeAlias = Callable[[Context, str, str, str], None]
+_TwoArgStep: TypeAlias = Callable[[Context, str, str], None]
+_OneArgStep: TypeAlias = Callable[[Context, str], None]
+_NoArgStep: TypeAlias = Callable[[Context], None]
+_Step: TypeAlias = _NoArgStep | _OneArgStep | _TwoArgStep | _ThreeArgStep
+
+def given(phrase: str) -> Callable[[_Step], _Step]: ...
+def when(phrase: str) -> Callable[[_Step], _Step]: ...
+def then(phrase: str) -> Callable[[_Step], _Step]: ...
diff --git a/typings/behave/runner.pyi b/typings/behave/runner.pyi
new file mode 100644
index 000000000..aaea74dad
--- /dev/null
+++ b/typings/behave/runner.pyi
@@ -0,0 +1,3 @@
+from types import SimpleNamespace
+
+class Context(SimpleNamespace): ...
diff --git a/typings/lxml/_types.pyi b/typings/lxml/_types.pyi
index a16fec3dd..34d2095db 100644
--- a/typings/lxml/_types.pyi
+++ b/typings/lxml/_types.pyi
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Any, Callable, Collection, Mapping, Protocol, TypeVar
+from typing import Any, Callable, Collection, Literal, Mapping, Protocol, TypeVar
from typing_extensions import TypeAlias
@@ -25,6 +25,8 @@ _NSMapArg = Mapping[None, str] | Mapping[str, str] | Mapping[str | None, str]
_NonDefaultNSMapArg = Mapping[str, str]
+_OutputMethodArg = Literal["html", "text", "xml"]
+
_TagName: TypeAlias = str
_TagSelector: TypeAlias = _TagName | Callable[..., _Element]
diff --git a/typings/lxml/etree/_module_func.pyi b/typings/lxml/etree/_module_func.pyi
index 067b25ce9..e2910f503 100644
--- a/typings/lxml/etree/_module_func.pyi
+++ b/typings/lxml/etree/_module_func.pyi
@@ -2,18 +2,37 @@
from __future__ import annotations
-from .._types import _ElementOrTree
+from typing import Literal, overload
+
+from .._types import _ElementOrTree, _OutputMethodArg
from ..etree import HTMLParser, XMLParser
from ._element import _Element
def fromstring(text: str | bytes, parser: XMLParser | HTMLParser) -> _Element: ...
-# Under XML Canonicalization (C14N) mode, most arguments are ignored,
-# some arguments would even raise exception outright if specified.
-def tostring(
+# -- Native str, no XML declaration --
+@overload
+def tostring( # type: ignore[overload-overlap]
element_or_tree: _ElementOrTree,
*,
- encoding: str | type[str] | None = None,
+ encoding: type[str] | Literal["unicode"],
+ method: _OutputMethodArg = "xml",
pretty_print: bool = False,
with_tail: bool = True,
+ standalone: bool | None = None,
+ doctype: str | None = None,
) -> str: ...
+
+# -- bytes, str encoded with `encoding`, no XML declaration --
+@overload
+def tostring(
+ element_or_tree: _ElementOrTree,
+ *,
+ encoding: str | None = None,
+ method: _OutputMethodArg = "xml",
+ xml_declaration: bool | None = None,
+ pretty_print: bool = False,
+ with_tail: bool = True,
+ standalone: bool | None = None,
+ doctype: str | None = None,
+) -> bytes: ...