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.
Files changed (86) hide show
  1. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/PKG-INFO +1 -1
  2. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/__init__.py +1 -1
  3. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/client.py +25 -12
  4. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/commands.py +17 -0
  5. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/__init__.py +45 -6
  6. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pyproject.toml +1 -1
  7. athena_python_pptx-0.4.0/uv.lock +1163 -0
  8. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/.gitignore +0 -0
  9. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/API_PARITY_REPORT.md +0 -0
  10. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/CHANGELOG.md +0 -0
  11. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/CLAUDE.md +0 -0
  12. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/DEV-GUIDE.md +0 -0
  13. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/PARITY_QUESTIONS.md +0 -0
  14. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/PUBLISHING.md +0 -0
  15. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/README.md +0 -0
  16. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/docs/API_PARITY_EXCEPTIONS.md +0 -0
  17. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/docs/athena-api.json +0 -0
  18. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/docs/athena-api.md +0 -0
  19. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/_athena_extension.py +0 -0
  20. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/_ptc.py +0 -0
  21. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/_references.py +0 -0
  22. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/action.py +0 -0
  23. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/batching.py +0 -0
  24. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/__init__.py +0 -0
  25. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/axis.py +0 -0
  26. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/category.py +0 -0
  27. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/chart.py +0 -0
  28. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/data.py +0 -0
  29. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/datalabel.py +0 -0
  30. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/legend.py +0 -0
  31. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/marker.py +0 -0
  32. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/plot.py +0 -0
  33. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/point.py +0 -0
  34. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/series.py +0 -0
  35. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/chart/xlsx.py +0 -0
  36. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/decorators.py +0 -0
  37. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/__init__.py +0 -0
  38. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/chtfmt.py +0 -0
  39. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/color.py +0 -0
  40. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/effect.py +0 -0
  41. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/fill.py +0 -0
  42. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/dml/line.py +0 -0
  43. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/docgen.py +0 -0
  44. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/__init__.py +0 -0
  45. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/action.py +0 -0
  46. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/chart.py +0 -0
  47. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/dml.py +0 -0
  48. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/lang.py +0 -0
  49. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/shapes.py +0 -0
  50. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/enum/text.py +0 -0
  51. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/errors.py +0 -0
  52. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/exc.py +0 -0
  53. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/media.py +0 -0
  54. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/package.py +0 -0
  55. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/__init__.py +0 -0
  56. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/_base.py +0 -0
  57. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/chart.py +0 -0
  58. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/coreprops.py +0 -0
  59. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/embeddedpackage.py +0 -0
  60. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/image.py +0 -0
  61. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/media.py +0 -0
  62. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/presentation.py +0 -0
  63. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/parts/slide.py +0 -0
  64. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/presentation.py +0 -0
  65. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/autoshape.py +0 -0
  66. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/base.py +0 -0
  67. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/connector.py +0 -0
  68. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/freeform.py +0 -0
  69. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/graphfrm.py +0 -0
  70. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/group.py +0 -0
  71. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/picture.py +0 -0
  72. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/placeholder.py +0 -0
  73. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shapes/shapetree.py +0 -0
  74. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/shared.py +0 -0
  75. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/slide.py +0 -0
  76. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/slides.py +0 -0
  77. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/spec.py +0 -0
  78. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/table.py +0 -0
  79. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/text/__init__.py +0 -0
  80. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/text/fonts.py +0 -0
  81. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/text/layout.py +0 -0
  82. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/text/text.py +0 -0
  83. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/types.py +0 -0
  84. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/typing.py +0 -0
  85. {athena_python_pptx-0.3.1 → athena_python_pptx-0.4.0}/pptx/units.py +0 -0
  86. {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.1
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
@@ -132,7 +132,7 @@ def flush_all() -> None:
132
132
  _active_buffers[:] = alive
133
133
 
134
134
 
135
- __version__ = "0.3.1"
135
+ __version__ = "0.4.0"
136
136
 
137
137
  __all__ = [
138
138
  # Main entry point
@@ -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, uses ATHENA_PPTX_API_KEY environment variable.
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 environment variable must be set"
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
- raise AuthenticationError("Invalid or expired API key")
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('.pptx') or name.endswith('.potx'):
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('.pptx') or name.endswith('.potx'):
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
- self._position = value
3051
- if value is not None:
3052
- self._emit(position=str(value))
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
- self._position = value
3361
- if value is not None:
3362
- self._emit_style(position=value)
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.3.1"
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"