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.
Files changed (52) hide show
  1. paper_pptx_doctor/__init__.py +135 -0
  2. paper_pptx_doctor/__main__.py +5 -0
  3. pptx2/__init__.py +19 -1
  4. pptx2/_ownership.py +67 -0
  5. pptx2/_transaction.py +666 -0
  6. pptx2/_version.py +20 -0
  7. pptx2/_zipguard.py +932 -0
  8. pptx2/chart/chart.py +232 -0
  9. pptx2/compose/__init__.py +23 -14
  10. pptx2/compose/deck_compose.py +1203 -0
  11. pptx2/design/blocks.py +433 -0
  12. pptx2/design/palettes.py +217 -0
  13. pptx2/diff.py +1116 -0
  14. pptx2/edit.py +612 -0
  15. pptx2/enum/text.py +28 -0
  16. pptx2/errors.py +105 -0
  17. pptx2/hf.py +344 -0
  18. pptx2/inspect.py +1993 -0
  19. pptx2/math.py +5 -0
  20. pptx2/opc/package.py +236 -3
  21. pptx2/opc/serialized.py +62 -5
  22. pptx2/oxml/__init__.py +26 -0
  23. pptx2/oxml/presentation.py +32 -30
  24. pptx2/oxml/simpletypes.py +89 -0
  25. pptx2/oxml/slide.py +57 -2
  26. pptx2/oxml/table.py +38 -0
  27. pptx2/oxml/text.py +83 -3
  28. pptx2/package.py +642 -0
  29. pptx2/parts/presentation.py +6 -11
  30. pptx2/presentation.py +211 -37
  31. pptx2/rebind.py +531 -0
  32. pptx2/render.py +99 -0
  33. pptx2/shapes/picture.py +103 -10
  34. pptx2/shapes/shapetree.py +434 -65
  35. pptx2/skill/SKILL.md +110 -20
  36. pptx2/skill/references/compose.md +39 -7
  37. pptx2/skill/references/design.md +35 -0
  38. pptx2/skill/references/editing.md +117 -0
  39. pptx2/skill/references/render.md +20 -0
  40. pptx2/skill/references/slide-design.md +495 -0
  41. pptx2/slide.py +580 -100
  42. pptx2/slideops.py +444 -0
  43. pptx2/table.py +530 -22
  44. pptx2/text/bullet.py +261 -0
  45. pptx2/text/text.py +239 -0
  46. {python_pptx2-2.20.0.dist-info → python_pptx2-3.1.0.dist-info}/METADATA +2 -2
  47. {python_pptx2-2.20.0.dist-info → python_pptx2-3.1.0.dist-info}/RECORD +51 -32
  48. {python_pptx2-2.20.0.dist-info → python_pptx2-3.1.0.dist-info}/entry_points.txt +1 -0
  49. python_pptx2-3.1.0.dist-info/top_level.txt +2 -0
  50. python_pptx2-2.20.0.dist-info/top_level.txt +0 -1
  51. {python_pptx2-2.20.0.dist-info → python_pptx2-3.1.0.dist-info}/WHEEL +0 -0
  52. {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"]
@@ -0,0 +1,5 @@
1
+ """Run ``paper-pptx-doctor`` with ``python -m paper_pptx_doctor``."""
2
+
3
+ from . import main
4
+
5
+ raise SystemExit(main())
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__ = "2.20.0"
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
+ )