athena-python-pptx 0.3.1__tar.gz → 0.4.0__tar.gz
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.
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/PKG-INFO +1 -1
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/__init__.py +1 -1
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/client.py +25 -12
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/commands.py +17 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/__init__.py +45 -6
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pyproject.toml +1 -1
- athena_python_pptx-0.4.0/uv.lock +1163 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/.gitignore +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/API_PARITY_REPORT.md +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/CHANGELOG.md +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/CLAUDE.md +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/DEV-GUIDE.md +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/PUBLISHING.md +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/README.md +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/docs/API_PARITY_EXCEPTIONS.md +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/_athena_extension.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/_ptc.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/_references.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/action.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/batching.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/data.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/chart.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/dml.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/shapes.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/errors.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/exc.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/media.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/package.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/chart.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/image.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/presentation.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shared.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/slide.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/slides.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/spec.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/table.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/text/__init__.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/types.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/typing.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/units.py +0 -0
- {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Drop-in replacement for python-pptx that connects to PPTX Studio for real-time collaboration
|
|
5
5
|
Project-URL: Homepage, https://github.com/pptx-studio/python-sdk
|
|
6
6
|
Project-URL: Documentation, https://docs.pptx-studio.com/sdk/python
|
|
@@ -72,7 +72,11 @@ class Client:
|
|
|
72
72
|
base_url: Base URL of the API (e.g., "https://api.pptx-studio.com").
|
|
73
73
|
If not provided, uses ATHENA_PPTX_BASE_URL environment variable.
|
|
74
74
|
api_key: Optional API key for authentication.
|
|
75
|
-
If not provided,
|
|
75
|
+
If not provided, falls back to the ATHENA_PPTX_API_KEY
|
|
76
|
+
environment variable, then to ATHENA_API_KEY (the canonical
|
|
77
|
+
Athena user API key — same value, accepted as a fallback so
|
|
78
|
+
code running in sandboxes that only inject ATHENA_API_KEY
|
|
79
|
+
still authenticates).
|
|
76
80
|
timeout: Request timeout in seconds
|
|
77
81
|
|
|
78
82
|
Raises:
|
|
@@ -83,11 +87,13 @@ class Client:
|
|
|
83
87
|
base_url = os.environ.get("ATHENA_PPTX_BASE_URL")
|
|
84
88
|
if base_url is None:
|
|
85
89
|
raise ValueError(
|
|
86
|
-
"base_url must be provided or ATHENA_PPTX_BASE_URL
|
|
90
|
+
"base_url must be provided or the ATHENA_PPTX_BASE_URL "
|
|
91
|
+
"environment variable must be set. Example: "
|
|
92
|
+
'ATHENA_PPTX_BASE_URL="https://pptx-studio.prd.athenaintel.com".'
|
|
87
93
|
)
|
|
88
94
|
|
|
89
95
|
if api_key is None:
|
|
90
|
-
api_key = os.environ.get("ATHENA_PPTX_API_KEY")
|
|
96
|
+
api_key = os.environ.get("ATHENA_PPTX_API_KEY") or os.environ.get("ATHENA_API_KEY")
|
|
91
97
|
|
|
92
98
|
self.base_url = base_url.rstrip("/")
|
|
93
99
|
self.api_key = api_key
|
|
@@ -129,7 +135,18 @@ class Client:
|
|
|
129
135
|
def _handle_response(self, response: requests.Response) -> Any:
|
|
130
136
|
"""Handle API response, raising appropriate errors."""
|
|
131
137
|
if response.status_code == 401:
|
|
132
|
-
|
|
138
|
+
if not self.api_key:
|
|
139
|
+
raise AuthenticationError(
|
|
140
|
+
"No API key was sent. Pass api_key= to Presentation(...) "
|
|
141
|
+
"or set ATHENA_PPTX_API_KEY (or ATHENA_API_KEY as a "
|
|
142
|
+
"fallback) in the environment."
|
|
143
|
+
)
|
|
144
|
+
raise AuthenticationError(
|
|
145
|
+
"Invalid or expired API key. The pptx-studio API rejected the "
|
|
146
|
+
"credentials. Verify ATHENA_PPTX_API_KEY (or the ATHENA_API_KEY "
|
|
147
|
+
"fallback) is set to a valid Athena user API key for the "
|
|
148
|
+
"current workspace."
|
|
149
|
+
)
|
|
133
150
|
|
|
134
151
|
if response.status_code == 409:
|
|
135
152
|
data = response.json() if response.text else {}
|
|
@@ -416,7 +433,7 @@ class Client:
|
|
|
416
433
|
if name is None:
|
|
417
434
|
name = os.path.basename(file_path)
|
|
418
435
|
# Remove extension
|
|
419
|
-
if name.endswith(
|
|
436
|
+
if name.endswith(".pptx") or name.endswith(".potx"):
|
|
420
437
|
name = name[:-5]
|
|
421
438
|
|
|
422
439
|
# Step 1: Create deck to get presigned URL
|
|
@@ -496,7 +513,7 @@ class Client:
|
|
|
496
513
|
# Use filename as name if not provided
|
|
497
514
|
if name is None:
|
|
498
515
|
name = os.path.basename(file_path)
|
|
499
|
-
if name.endswith(
|
|
516
|
+
if name.endswith(".pptx") or name.endswith(".potx"):
|
|
500
517
|
name = name[:-5]
|
|
501
518
|
|
|
502
519
|
# Step 1: Create deck to get presigned URL
|
|
@@ -800,9 +817,7 @@ class Client:
|
|
|
800
817
|
return self._download(download_url)
|
|
801
818
|
|
|
802
819
|
if status["status"] in ("failed", "error"):
|
|
803
|
-
raise ExportError(
|
|
804
|
-
status.get("error", "Export failed"), job_id
|
|
805
|
-
)
|
|
820
|
+
raise ExportError(status.get("error", "Export failed"), job_id)
|
|
806
821
|
|
|
807
822
|
time.sleep(poll_interval)
|
|
808
823
|
|
|
@@ -870,9 +885,7 @@ class Client:
|
|
|
870
885
|
return self._download(image_url)
|
|
871
886
|
|
|
872
887
|
if status["status"] in ("failed", "error"):
|
|
873
|
-
raise RenderError(
|
|
874
|
-
status.get("error", "Render failed"), job_id
|
|
875
|
-
)
|
|
888
|
+
raise RenderError(status.get("error", "Render failed"), job_id)
|
|
876
889
|
|
|
877
890
|
time.sleep(poll_interval)
|
|
878
891
|
|
|
@@ -1464,6 +1464,23 @@ class SetGradientFill(Command):
|
|
|
1464
1464
|
"stops"
|
|
1465
1465
|
)
|
|
1466
1466
|
|
|
1467
|
+
def to_dict(self) -> dict[str, Any]:
|
|
1468
|
+
"""Serialize, camelCasing each stop's ``color_hex`` to ``colorHex`` for the server schema."""
|
|
1469
|
+
result = super().to_dict()
|
|
1470
|
+
if self.stops is not None:
|
|
1471
|
+
camel_stops: list[dict[str, Any]] = []
|
|
1472
|
+
for stop in self.stops:
|
|
1473
|
+
camel: dict[str, Any] = {
|
|
1474
|
+
"position": stop.get("position"),
|
|
1475
|
+
"colorHex": stop.get("color_hex"),
|
|
1476
|
+
}
|
|
1477
|
+
transparency = stop.get("transparency")
|
|
1478
|
+
if transparency is not None:
|
|
1479
|
+
camel["transparency"] = transparency
|
|
1480
|
+
camel_stops.append(camel)
|
|
1481
|
+
result["stops"] = camel_stops
|
|
1482
|
+
return result
|
|
1483
|
+
|
|
1467
1484
|
|
|
1468
1485
|
@dataclass
|
|
1469
1486
|
class SetShapeZOrder(Command):
|
|
@@ -2963,6 +2963,43 @@ class _ChartPlotCollection:
|
|
|
2963
2963
|
# (rather than in their own file) so they're visible from the chart
|
|
2964
2964
|
# adapters above.
|
|
2965
2965
|
|
|
2966
|
+
# python-pptx ``XL_DATA_LABEL_POSITION`` (IntEnum) -> the OOXML ``dLblPos``
|
|
2967
|
+
# string symbols the server accepts. Assigning the enum member directly
|
|
2968
|
+
# (``data_labels.position = XL_LABEL_POSITION.OUTSIDE_END``) must serialize to
|
|
2969
|
+
# ``'outEnd'`` rather than the raw int ``2``, otherwise the commands endpoint
|
|
2970
|
+
# rejects the patch with a 400. The accepted set is mirrored from
|
|
2971
|
+
# ``packages/chart-core/src/schema.ts`` (``'ctr'|'inEnd'|'inBase'|'outEnd'|
|
|
2972
|
+
# 'bestFit'|'t'|'b'|'l'|'r'``). Mirrors the ``Legend._POSITION_INT_TO_STR``
|
|
2973
|
+
# pattern.
|
|
2974
|
+
_DATA_LABEL_POSITION_INT_TO_STR = {
|
|
2975
|
+
-4108: 'ctr', # CENTER
|
|
2976
|
+
3: 'inEnd', # INSIDE_END
|
|
2977
|
+
4: 'inBase', # INSIDE_BASE
|
|
2978
|
+
2: 'outEnd', # OUTSIDE_END
|
|
2979
|
+
5: 'bestFit', # BEST_FIT
|
|
2980
|
+
0: 't', # ABOVE
|
|
2981
|
+
1: 'b', # BELOW
|
|
2982
|
+
-4131: 'l', # LEFT
|
|
2983
|
+
-4152: 'r', # RIGHT
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
|
|
2987
|
+
def _coerce_data_label_position(value: Any) -> Optional[str]:
|
|
2988
|
+
"""Normalize a data-label position to its OOXML ``dLblPos`` string.
|
|
2989
|
+
|
|
2990
|
+
Accepts an ``XL_DATA_LABEL_POSITION`` member (or raw int), in which case
|
|
2991
|
+
it maps to the OOXML symbol, or an already-valid string, which passes
|
|
2992
|
+
through unchanged. Unmappable ints resolve to ``None`` so no invalid
|
|
2993
|
+
patch is emitted (matching ``Legend.position``'s behavior)."""
|
|
2994
|
+
if value is None:
|
|
2995
|
+
return None
|
|
2996
|
+
if isinstance(value, bool):
|
|
2997
|
+
return None
|
|
2998
|
+
if isinstance(value, int):
|
|
2999
|
+
return _DATA_LABEL_POSITION_INT_TO_STR.get(int(value))
|
|
3000
|
+
return str(value)
|
|
3001
|
+
|
|
3002
|
+
|
|
2966
3003
|
class DataLabel:
|
|
2967
3004
|
"""python-pptx parity for a single data label on a chart series.
|
|
2968
3005
|
|
|
@@ -3047,9 +3084,10 @@ class DataLabel:
|
|
|
3047
3084
|
|
|
3048
3085
|
@position.setter
|
|
3049
3086
|
def position(self, value: Optional[str]) -> None:
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3087
|
+
coerced = _coerce_data_label_position(value)
|
|
3088
|
+
self._position = coerced
|
|
3089
|
+
if coerced is not None:
|
|
3090
|
+
self._emit(position=coerced)
|
|
3053
3091
|
|
|
3054
3092
|
@property
|
|
3055
3093
|
@athena_extension(
|
|
@@ -3357,9 +3395,10 @@ class DataLabels:
|
|
|
3357
3395
|
|
|
3358
3396
|
@position.setter
|
|
3359
3397
|
def position(self, value: Optional[str]) -> None:
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3398
|
+
coerced = _coerce_data_label_position(value)
|
|
3399
|
+
self._position = coerced
|
|
3400
|
+
if coerced is not None:
|
|
3401
|
+
self._emit_style(position=coerced)
|
|
3363
3402
|
|
|
3364
3403
|
@property
|
|
3365
3404
|
def show_category_name(self) -> bool:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "athena-python-pptx"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Drop-in replacement for python-pptx that connects to PPTX Studio for real-time collaboration"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|