python-pptx2 2.20.0__py3-none-any.whl → 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- paper_pptx_doctor/__init__.py +135 -0
- paper_pptx_doctor/__main__.py +5 -0
- pptx2/__init__.py +19 -1
- pptx2/_ownership.py +67 -0
- pptx2/_transaction.py +666 -0
- pptx2/_version.py +20 -0
- pptx2/_zipguard.py +932 -0
- pptx2/chart/chart.py +232 -0
- pptx2/compose/__init__.py +23 -14
- pptx2/compose/deck_compose.py +1203 -0
- pptx2/design/blocks.py +433 -0
- pptx2/design/palettes.py +217 -0
- pptx2/diff.py +1116 -0
- pptx2/edit.py +612 -0
- pptx2/enum/text.py +28 -0
- pptx2/errors.py +105 -0
- pptx2/hf.py +344 -0
- pptx2/inspect.py +1993 -0
- pptx2/math.py +5 -0
- pptx2/opc/package.py +236 -3
- pptx2/opc/serialized.py +62 -5
- pptx2/oxml/__init__.py +26 -0
- pptx2/oxml/presentation.py +32 -30
- pptx2/oxml/simpletypes.py +89 -0
- pptx2/oxml/slide.py +57 -2
- pptx2/oxml/table.py +38 -0
- pptx2/oxml/text.py +83 -3
- pptx2/package.py +642 -0
- pptx2/parts/presentation.py +6 -11
- pptx2/presentation.py +211 -37
- pptx2/rebind.py +531 -0
- pptx2/render.py +99 -0
- pptx2/shapes/picture.py +103 -10
- pptx2/shapes/shapetree.py +434 -65
- pptx2/skill/SKILL.md +110 -20
- pptx2/skill/references/compose.md +39 -7
- pptx2/skill/references/design.md +35 -0
- pptx2/skill/references/editing.md +117 -0
- pptx2/skill/references/render.md +20 -0
- pptx2/skill/references/slide-design.md +495 -0
- pptx2/slide.py +580 -100
- pptx2/slideops.py +444 -0
- pptx2/table.py +530 -22
- pptx2/text/bullet.py +261 -0
- pptx2/text/text.py +239 -0
- {python_pptx2-2.20.0.dist-info → python_pptx2-3.1.0.dist-info}/METADATA +2 -2
- {python_pptx2-2.20.0.dist-info → python_pptx2-3.1.0.dist-info}/RECORD +51 -32
- {python_pptx2-2.20.0.dist-info → python_pptx2-3.1.0.dist-info}/entry_points.txt +1 -0
- python_pptx2-3.1.0.dist-info/top_level.txt +2 -0
- python_pptx2-2.20.0.dist-info/top_level.txt +0 -1
- {python_pptx2-2.20.0.dist-info → python_pptx2-3.1.0.dist-info}/WHEEL +0 -0
- {python_pptx2-2.20.0.dist-info → python_pptx2-3.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Verify that the installed ``pptx2`` import belongs to ``python-pptx2``.
|
|
2
|
+
|
|
3
|
+
Adapted from paper-pptx's distribution doctor: same wheel-integrity
|
|
4
|
+
checks (RECORD hashes for every ``pptx2`` package file), retargeted at
|
|
5
|
+
the ``python-pptx2`` distribution and its ``__version__`` sentinel.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import base64
|
|
11
|
+
import csv
|
|
12
|
+
import hashlib
|
|
13
|
+
import hmac
|
|
14
|
+
import importlib
|
|
15
|
+
import sys
|
|
16
|
+
from importlib.metadata import Distribution, PackageNotFoundError, distribution
|
|
17
|
+
from io import StringIO
|
|
18
|
+
from pathlib import Path, PurePosixPath
|
|
19
|
+
from typing import Iterable, Optional, Tuple
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DoctorError(RuntimeError):
|
|
23
|
+
"""The installed ``pptx2`` package cannot be trusted as ``python-pptx2``."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_REMEDY = "python -m pip install --force-reinstall python-pptx2"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def verify_install() -> str:
|
|
30
|
+
"""Verify distribution ownership, installed bytes, and the version sentinel.
|
|
31
|
+
|
|
32
|
+
Returns the installed ``python-pptx2`` version. Raises :class:`DoctorError`
|
|
33
|
+
without importing ``pptx2`` until its wheel-owned files have been checked.
|
|
34
|
+
"""
|
|
35
|
+
dist = _installed_distribution("python-pptx2")
|
|
36
|
+
if dist is None:
|
|
37
|
+
raise DoctorError("python-pptx2 distribution metadata is missing")
|
|
38
|
+
|
|
39
|
+
_verify_pptx_record(dist)
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
pptx2 = importlib.import_module("pptx2")
|
|
43
|
+
except Exception as exc:
|
|
44
|
+
raise DoctorError(f"pptx2 cannot be imported: {exc}") from exc
|
|
45
|
+
|
|
46
|
+
sentinel = getattr(pptx2, "__version__", None)
|
|
47
|
+
if sentinel is None:
|
|
48
|
+
raise DoctorError("pptx2.__version__ is missing")
|
|
49
|
+
if sentinel != dist.version:
|
|
50
|
+
raise DoctorError(
|
|
51
|
+
"pptx2.__version__ does not match the installed python-pptx2 "
|
|
52
|
+
f"version ({sentinel!r} != {dist.version!r})"
|
|
53
|
+
)
|
|
54
|
+
return dist.version
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def main() -> int:
|
|
58
|
+
"""Console entry point for ``paper-pptx-doctor``."""
|
|
59
|
+
try:
|
|
60
|
+
version = verify_install()
|
|
61
|
+
except DoctorError as exc:
|
|
62
|
+
print(f"paper-pptx-doctor: FAIL: {exc}", file=sys.stderr)
|
|
63
|
+
print(f"Remedy: {_REMEDY}", file=sys.stderr)
|
|
64
|
+
return 1
|
|
65
|
+
print(f"paper-pptx-doctor: OK (python-pptx2 {version})")
|
|
66
|
+
return 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _installed_distribution(name: str) -> Optional[Distribution]:
|
|
70
|
+
try:
|
|
71
|
+
return distribution(name)
|
|
72
|
+
except PackageNotFoundError:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _verify_pptx_record(dist: Distribution) -> None:
|
|
77
|
+
record = dist.read_text("RECORD")
|
|
78
|
+
if record is None:
|
|
79
|
+
raise DoctorError("python-pptx2 RECORD is missing")
|
|
80
|
+
|
|
81
|
+
entries = tuple(
|
|
82
|
+
(relative_path, hash_spec)
|
|
83
|
+
for relative_path, hash_spec in _pptx_record_entries(record)
|
|
84
|
+
if hash_spec
|
|
85
|
+
)
|
|
86
|
+
if not entries:
|
|
87
|
+
raise DoctorError("python-pptx2 RECORD has no hashed pptx2 package files")
|
|
88
|
+
|
|
89
|
+
for relative_path, hash_spec in entries:
|
|
90
|
+
path = Path(dist.locate_file(relative_path))
|
|
91
|
+
if not path.is_file():
|
|
92
|
+
raise DoctorError(f"python-pptx2 file is missing: {relative_path}")
|
|
93
|
+
algorithm, expected = _parse_hash(hash_spec, relative_path)
|
|
94
|
+
actual = _file_digest(path, algorithm)
|
|
95
|
+
if not hmac.compare_digest(actual, expected):
|
|
96
|
+
raise DoctorError(f"python-pptx2 file hash mismatch: {relative_path}")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _pptx_record_entries(record: str) -> Iterable[Tuple[PurePosixPath, str]]:
|
|
100
|
+
for row in csv.reader(StringIO(record)):
|
|
101
|
+
if len(row) != 3:
|
|
102
|
+
raise DoctorError("python-pptx2 RECORD contains a malformed row")
|
|
103
|
+
raw_path, hash_spec, _size = row
|
|
104
|
+
path = PurePosixPath(raw_path)
|
|
105
|
+
if not path.parts or path.parts[0] != "pptx2":
|
|
106
|
+
continue
|
|
107
|
+
if path.is_absolute() or ".." in path.parts:
|
|
108
|
+
raise DoctorError(f"python-pptx2 RECORD contains an unsafe path: {raw_path}")
|
|
109
|
+
yield path, hash_spec
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _parse_hash(hash_spec: str, relative_path: PurePosixPath) -> Tuple[str, str]:
|
|
113
|
+
try:
|
|
114
|
+
algorithm, expected = hash_spec.split("=", 1)
|
|
115
|
+
hashlib.new(algorithm)
|
|
116
|
+
except (TypeError, ValueError):
|
|
117
|
+
raise DoctorError(
|
|
118
|
+
f"python-pptx2 RECORD has an invalid hash for {relative_path}"
|
|
119
|
+
) from None
|
|
120
|
+
if not expected:
|
|
121
|
+
raise DoctorError(
|
|
122
|
+
f"python-pptx2 RECORD has an invalid hash for {relative_path}"
|
|
123
|
+
)
|
|
124
|
+
return algorithm, expected.rstrip("=")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _file_digest(path: Path, algorithm: str) -> str:
|
|
128
|
+
digest = hashlib.new(algorithm)
|
|
129
|
+
with path.open("rb") as stream:
|
|
130
|
+
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
|
131
|
+
digest.update(chunk)
|
|
132
|
+
return base64.urlsafe_b64encode(digest.digest()).rstrip(b"=").decode("ascii")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
__all__ = ["DoctorError", "main", "verify_install"]
|
pptx2/__init__.py
CHANGED
|
@@ -9,6 +9,14 @@ import pptx2.exc as exceptions
|
|
|
9
9
|
from pptx2.api import Presentation
|
|
10
10
|
from pptx2.audit import AuditReport, audit
|
|
11
11
|
from pptx2.geometry import BBox
|
|
12
|
+
from pptx2.design.blocks import (
|
|
13
|
+
Card,
|
|
14
|
+
FittedPicture,
|
|
15
|
+
add_bullets,
|
|
16
|
+
add_card,
|
|
17
|
+
add_picture_fit,
|
|
18
|
+
)
|
|
19
|
+
from pptx2.design.palettes import PALETTES, Palette, palette
|
|
12
20
|
from pptx2.design.components import (
|
|
13
21
|
ArticleCard,
|
|
14
22
|
Gauge,
|
|
@@ -57,7 +65,7 @@ from pptx2.parts.slide import (
|
|
|
57
65
|
if TYPE_CHECKING:
|
|
58
66
|
from pptx2.opc.package import Part
|
|
59
67
|
|
|
60
|
-
__version__ = "
|
|
68
|
+
__version__ = "3.1.0"
|
|
61
69
|
|
|
62
70
|
sys.modules["pptx2.exceptions"] = exceptions
|
|
63
71
|
del sys
|
|
@@ -91,6 +99,16 @@ __all__ = [
|
|
|
91
99
|
"StatusPill",
|
|
92
100
|
"StatStrip",
|
|
93
101
|
"ArticleCard",
|
|
102
|
+
# Everyday slide blocks (hex-driven; no token setup required).
|
|
103
|
+
"add_card",
|
|
104
|
+
"add_bullets",
|
|
105
|
+
"add_picture_fit",
|
|
106
|
+
"Card",
|
|
107
|
+
"FittedPicture",
|
|
108
|
+
# Curated colour sets.
|
|
109
|
+
"Palette",
|
|
110
|
+
"PALETTES",
|
|
111
|
+
"palette",
|
|
94
112
|
]
|
|
95
113
|
|
|
96
114
|
content_type_to_part_class_map: dict[str, type[Part]] = {
|
pptx2/_ownership.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Attachment checks for live shape proxies used by paper-pptx operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def require_shape_attached(shape, *, argument: str = "shape") -> None:
|
|
7
|
+
"""Refuse a shape proxy whose XML is no longer attached to its remembered part."""
|
|
8
|
+
from pptx2.errors import TargetNotFoundError
|
|
9
|
+
|
|
10
|
+
element = getattr(shape, "_element", None)
|
|
11
|
+
try:
|
|
12
|
+
part_root = shape.part._element
|
|
13
|
+
except (AttributeError, ValueError):
|
|
14
|
+
part_root = None
|
|
15
|
+
root = element
|
|
16
|
+
while root is not None and root.getparent() is not None:
|
|
17
|
+
root = root.getparent()
|
|
18
|
+
if element is None or part_root is None or root is not part_root:
|
|
19
|
+
raise TargetNotFoundError(
|
|
20
|
+
"%s is stale: its shape was removed from the presentation" % argument
|
|
21
|
+
)
|
|
22
|
+
require_part_reachable(shape.part, argument=argument)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def require_element_attached(element, part, *, argument: str) -> None:
|
|
26
|
+
"""Refuse an element detached from the active root of its remembered part."""
|
|
27
|
+
from pptx2.errors import TargetNotFoundError
|
|
28
|
+
|
|
29
|
+
root = element
|
|
30
|
+
while root is not None and root.getparent() is not None:
|
|
31
|
+
root = root.getparent()
|
|
32
|
+
if root is not getattr(part, "_element", None):
|
|
33
|
+
raise TargetNotFoundError(
|
|
34
|
+
"%s is stale: its content was removed from the presentation" % argument
|
|
35
|
+
)
|
|
36
|
+
require_part_reachable(part, argument=argument)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def require_shape_tree_attached(shapes, *, argument: str = "target shape tree") -> None:
|
|
40
|
+
"""Refuse a shape collection that no longer wraps its part's active tree."""
|
|
41
|
+
from pptx2.errors import TargetNotFoundError
|
|
42
|
+
|
|
43
|
+
spTree = getattr(shapes, "_spTree", None)
|
|
44
|
+
part = shapes.part
|
|
45
|
+
require_element_attached(spTree, part, argument=argument)
|
|
46
|
+
live_spTree = part._element.cSld.spTree
|
|
47
|
+
if spTree is not live_spTree:
|
|
48
|
+
raise TargetNotFoundError(
|
|
49
|
+
"%s is stale: it is no longer the part's active shape tree" % argument
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def require_part_reachable(part, *, argument: str) -> None:
|
|
54
|
+
"""Refuse a part proxy no longer reachable from its package root."""
|
|
55
|
+
from pptx2.errors import TargetNotFoundError, UnsupportedStructureError
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
reachable = any(candidate is part for candidate in part.package.iter_parts())
|
|
59
|
+
except (AssertionError, AttributeError, KeyError, TypeError, ValueError) as exc:
|
|
60
|
+
raise UnsupportedStructureError(
|
|
61
|
+
"cannot verify %s ownership because the package relationship graph is broken (%s)"
|
|
62
|
+
% (argument, exc)
|
|
63
|
+
) from exc
|
|
64
|
+
if not reachable:
|
|
65
|
+
raise TargetNotFoundError(
|
|
66
|
+
"%s is stale: its package part is no longer in the presentation" % argument
|
|
67
|
+
)
|