pdfdancer-client-python 0.3.13__py3-none-any.whl → 3.0.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.
- pdfdancer/__init__.py +79 -22
- pdfdancer/_runtime_version.py +30 -0
- pdfdancer/_version.py +24 -0
- pdfdancer/image_builder.py +23 -3
- pdfdancer/models.py +127 -470
- pdfdancer/page_builder.py +6 -17
- pdfdancer/path_builder.py +127 -6
- pdfdancer/{pdfdancer_v1.py → pdfdancer_v2.py} +850 -1316
- pdfdancer/text_editing.py +1472 -0
- pdfdancer/types.py +94 -399
- pdfdancer_client_python-3.0.0.dist-info/METADATA +521 -0
- pdfdancer_client_python-3.0.0.dist-info/RECORD +18 -0
- {pdfdancer_client_python-0.3.13.dist-info → pdfdancer_client_python-3.0.0.dist-info}/WHEEL +1 -1
- pdfdancer/paragraph_builder.py +0 -554
- pdfdancer/text_line_builder.py +0 -290
- pdfdancer_client_python-0.3.13.dist-info/METADATA +0 -685
- pdfdancer_client_python-0.3.13.dist-info/RECORD +0 -17
- {pdfdancer_client_python-0.3.13.dist-info → pdfdancer_client_python-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {pdfdancer_client_python-0.3.13.dist-info → pdfdancer_client_python-3.0.0.dist-info}/licenses/NOTICE +0 -0
- {pdfdancer_client_python-0.3.13.dist-info → pdfdancer_client_python-3.0.0.dist-info}/top_level.txt +0 -0
pdfdancer/models.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Model classes for the PDFDancer Python client.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import math
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from enum import Enum
|
|
7
8
|
from typing import Any, ClassVar, Dict, List, Mapping, Optional, Tuple, Union
|
|
@@ -45,28 +46,51 @@ class PageSize:
|
|
|
45
46
|
height: float
|
|
46
47
|
|
|
47
48
|
_STANDARD_SIZES: ClassVar[Dict[str, Tuple[float, float]]] = {
|
|
49
|
+
"A0": (2384.0, 3370.0),
|
|
50
|
+
"A1": (1684.0, 2384.0),
|
|
51
|
+
"A2": (1191.0, 1684.0),
|
|
52
|
+
"A3": (842.0, 1191.0),
|
|
48
53
|
"A4": (595.0, 842.0),
|
|
54
|
+
"A5": (420.0, 595.0),
|
|
55
|
+
"A6": (298.0, 420.0),
|
|
56
|
+
"B4": (709.0, 1001.0),
|
|
57
|
+
"B5": (499.0, 709.0),
|
|
49
58
|
"LETTER": (612.0, 792.0),
|
|
50
59
|
"LEGAL": (612.0, 1008.0),
|
|
51
60
|
"TABLOID": (792.0, 1224.0),
|
|
52
|
-
"
|
|
53
|
-
"
|
|
61
|
+
"EXECUTIVE": (522.0, 756.0),
|
|
62
|
+
"POSTCARD": (288.0, 432.0),
|
|
63
|
+
"INDEX_3X5": (216.0, 360.0),
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
# Convenience aliases populated after class definition; annotated for type checkers.
|
|
67
|
+
A0: ClassVar["PageSize"]
|
|
68
|
+
A1: ClassVar["PageSize"]
|
|
69
|
+
A2: ClassVar["PageSize"]
|
|
70
|
+
A3: ClassVar["PageSize"]
|
|
57
71
|
A4: ClassVar["PageSize"]
|
|
72
|
+
A5: ClassVar["PageSize"]
|
|
73
|
+
A6: ClassVar["PageSize"]
|
|
74
|
+
B4: ClassVar["PageSize"]
|
|
75
|
+
B5: ClassVar["PageSize"]
|
|
58
76
|
LETTER: ClassVar["PageSize"]
|
|
59
77
|
LEGAL: ClassVar["PageSize"]
|
|
60
78
|
TABLOID: ClassVar["PageSize"]
|
|
61
|
-
|
|
62
|
-
|
|
79
|
+
EXECUTIVE: ClassVar["PageSize"]
|
|
80
|
+
POSTCARD: ClassVar["PageSize"]
|
|
81
|
+
INDEX_3X5: ClassVar["PageSize"]
|
|
63
82
|
|
|
64
83
|
def __post_init__(self) -> None:
|
|
65
84
|
if not isinstance(self.width, (int, float)) or not isinstance(
|
|
66
85
|
self.height, (int, float)
|
|
67
86
|
):
|
|
68
87
|
raise TypeError("Page width and height must be numeric")
|
|
69
|
-
if
|
|
88
|
+
if (
|
|
89
|
+
not math.isfinite(self.width)
|
|
90
|
+
or not math.isfinite(self.height)
|
|
91
|
+
or self.width <= 0
|
|
92
|
+
or self.height <= 0
|
|
93
|
+
):
|
|
70
94
|
raise ValueError("Page width and height must be positive values")
|
|
71
95
|
|
|
72
96
|
width = float(self.width)
|
|
@@ -82,7 +106,7 @@ class PageSize:
|
|
|
82
106
|
self, "name", normalized_name if normalized_name else None
|
|
83
107
|
)
|
|
84
108
|
|
|
85
|
-
def to_dict(self) ->
|
|
109
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
86
110
|
"""Convert to dictionary for JSON serialization."""
|
|
87
111
|
return {
|
|
88
112
|
"name": self.name,
|
|
@@ -127,14 +151,29 @@ class PageSize:
|
|
|
127
151
|
"""Return a list of supported standard page size names."""
|
|
128
152
|
return sorted(cls._STANDARD_SIZES.keys())
|
|
129
153
|
|
|
154
|
+
@classmethod
|
|
155
|
+
def from_dimensions(
|
|
156
|
+
cls, width: float, height: float, tolerance: float = 0.5
|
|
157
|
+
) -> "PageSize":
|
|
158
|
+
"""Recognize standard dimensions in portrait or rotated orientation."""
|
|
159
|
+
custom = cls(name=None, width=width, height=height)
|
|
160
|
+
for name, (standard_width, standard_height) in cls._STANDARD_SIZES.items():
|
|
161
|
+
direct = (
|
|
162
|
+
abs(standard_width - custom.width) < tolerance
|
|
163
|
+
and abs(standard_height - custom.height) < tolerance
|
|
164
|
+
)
|
|
165
|
+
rotated = (
|
|
166
|
+
abs(standard_width - custom.height) < tolerance
|
|
167
|
+
and abs(standard_height - custom.width) < tolerance
|
|
168
|
+
)
|
|
169
|
+
if direct or rotated:
|
|
170
|
+
return cls.from_name(name)
|
|
171
|
+
return custom
|
|
172
|
+
|
|
130
173
|
|
|
131
174
|
# Populate convenience constants for standard sizes.
|
|
132
|
-
|
|
133
|
-
PageSize
|
|
134
|
-
PageSize.LEGAL = PageSize.from_name("LEGAL")
|
|
135
|
-
PageSize.TABLOID = PageSize.from_name("TABLOID")
|
|
136
|
-
PageSize.A3 = PageSize.from_name("A3")
|
|
137
|
-
PageSize.A5 = PageSize.from_name("A5")
|
|
175
|
+
for _page_size_name in PageSize._STANDARD_SIZES:
|
|
176
|
+
setattr(PageSize, _page_size_name, PageSize.from_name(_page_size_name))
|
|
138
177
|
|
|
139
178
|
|
|
140
179
|
class Orientation(Enum):
|
|
@@ -191,19 +230,24 @@ class StandardFonts(Enum):
|
|
|
191
230
|
class ObjectType(Enum):
|
|
192
231
|
"""Server object type discriminator used in refs, requests, and snapshots."""
|
|
193
232
|
|
|
194
|
-
|
|
233
|
+
PDF = "PDF"
|
|
234
|
+
PAGE = "PAGE"
|
|
235
|
+
TEXT_ELEMENT = "TEXT_ELEMENT"
|
|
195
236
|
IMAGE = "IMAGE"
|
|
196
|
-
FORM_X_OBJECT = "FORM_X_OBJECT"
|
|
197
237
|
PATH = "PATH"
|
|
198
|
-
|
|
238
|
+
LINE = "LINE"
|
|
239
|
+
RECTANGLE = "RECTANGLE"
|
|
240
|
+
BEZIER = "BEZIER"
|
|
241
|
+
CLIPPING = "CLIPPING"
|
|
242
|
+
FORM_X_OBJECT = "FORM_X_OBJECT"
|
|
243
|
+
FORM_FIELD = "FORM_FIELD"
|
|
244
|
+
WORD = "WORD"
|
|
199
245
|
TEXT_LINE = "TEXT_LINE"
|
|
200
|
-
PAGE = "PAGE"
|
|
201
246
|
TEXT_FIELD = "TEXT_FIELD"
|
|
202
|
-
CHECK_BOX = "CHECK_BOX"
|
|
203
247
|
RADIO_BUTTON = "RADIO_BUTTON"
|
|
204
248
|
BUTTON = "BUTTON"
|
|
205
249
|
DROPDOWN = "DROPDOWN"
|
|
206
|
-
|
|
250
|
+
CHECKBOX = "CHECKBOX"
|
|
207
251
|
|
|
208
252
|
|
|
209
253
|
class PositionMode(Enum):
|
|
@@ -280,8 +324,8 @@ class Position:
|
|
|
280
324
|
|
|
281
325
|
Examples:
|
|
282
326
|
```python
|
|
283
|
-
# A point on page
|
|
284
|
-
pos = Position.at_page_coordinates(
|
|
327
|
+
# A point on page 1
|
|
328
|
+
pos = Position.at_page_coordinates(1, x=72, y=720)
|
|
285
329
|
|
|
286
330
|
# Search by name (e.g. a form field) and then move down 12 points
|
|
287
331
|
pos = Position.by_name("Email").move_y(-12)
|
|
@@ -343,13 +387,17 @@ class Position:
|
|
|
343
387
|
def move_x(self, x_offset: float) -> "Position":
|
|
344
388
|
"""Move the position horizontally by the specified offset."""
|
|
345
389
|
if self.bounding_rect:
|
|
346
|
-
self.at_coordinates(
|
|
390
|
+
self.at_coordinates(
|
|
391
|
+
Point(self.bounding_rect.get_x() + x_offset, self.bounding_rect.get_y())
|
|
392
|
+
)
|
|
347
393
|
return self
|
|
348
394
|
|
|
349
395
|
def move_y(self, y_offset: float) -> "Position":
|
|
350
396
|
"""Move the position vertically by the specified offset."""
|
|
351
397
|
if self.bounding_rect:
|
|
352
|
-
self.at_coordinates(
|
|
398
|
+
self.at_coordinates(
|
|
399
|
+
Point(self.bounding_rect.get_x(), self.bounding_rect.get_y() + y_offset)
|
|
400
|
+
)
|
|
353
401
|
return self
|
|
354
402
|
|
|
355
403
|
def x(self) -> Optional[float]:
|
|
@@ -374,7 +422,7 @@ class ObjectRef:
|
|
|
374
422
|
Usage:
|
|
375
423
|
- Instances are typically returned in snapshots or find results.
|
|
376
424
|
- Pass an `ObjectRef` to request objects such as `MoveRequest`, `DeleteRequest`,
|
|
377
|
-
`
|
|
425
|
+
`DeleteRequest`, `MoveRequest`, or `ModifyRequest`.
|
|
378
426
|
|
|
379
427
|
Example:
|
|
380
428
|
```python
|
|
@@ -404,17 +452,22 @@ class ObjectRef:
|
|
|
404
452
|
"""Returns the type classification of the referenced object."""
|
|
405
453
|
return self.type
|
|
406
454
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
455
|
+
@property
|
|
456
|
+
def object_type(self) -> ObjectType:
|
|
457
|
+
"""Return the object type using the public object naming convention."""
|
|
458
|
+
return self.type
|
|
459
|
+
|
|
460
|
+
@object_type.setter
|
|
461
|
+
def object_type(self, object_type: ObjectType) -> None:
|
|
462
|
+
"""Update the object type using the public object naming convention."""
|
|
463
|
+
self.type = object_type
|
|
413
464
|
|
|
465
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
466
|
+
"""Convert to dictionary for JSON serialization."""
|
|
414
467
|
return {
|
|
415
468
|
"internalId": self.internal_id,
|
|
416
469
|
"position": FindRequest._position_to_dict(self.position),
|
|
417
|
-
"type":
|
|
470
|
+
"type": self.type.value,
|
|
418
471
|
}
|
|
419
472
|
|
|
420
473
|
|
|
@@ -443,14 +496,27 @@ class Color:
|
|
|
443
496
|
b: int
|
|
444
497
|
a: int = 255 # Alpha channel, default fully opaque
|
|
445
498
|
|
|
446
|
-
|
|
499
|
+
BLACK: ClassVar["Color"]
|
|
500
|
+
WHITE: ClassVar["Color"]
|
|
501
|
+
RED: ClassVar["Color"]
|
|
502
|
+
|
|
503
|
+
def __post_init__(self) -> None:
|
|
447
504
|
for component in [self.r, self.g, self.b, self.a]:
|
|
448
|
-
if
|
|
505
|
+
if (
|
|
506
|
+
isinstance(component, bool)
|
|
507
|
+
or not isinstance(component, int)
|
|
508
|
+
or not 0 <= component <= 255
|
|
509
|
+
):
|
|
449
510
|
raise ValueError(
|
|
450
511
|
f"Color component must be between 0 and 255, got {component}"
|
|
451
512
|
)
|
|
452
513
|
|
|
453
514
|
|
|
515
|
+
Color.BLACK = Color(0, 0, 0)
|
|
516
|
+
Color.WHITE = Color(255, 255, 255)
|
|
517
|
+
Color.RED = Color(255, 0, 0)
|
|
518
|
+
|
|
519
|
+
|
|
454
520
|
@dataclass
|
|
455
521
|
class Font:
|
|
456
522
|
"""Font face and size.
|
|
@@ -474,7 +540,7 @@ class Font:
|
|
|
474
540
|
name: str
|
|
475
541
|
size: float
|
|
476
542
|
|
|
477
|
-
def __post_init__(self):
|
|
543
|
+
def __post_init__(self) -> None:
|
|
478
544
|
if self.size <= 0:
|
|
479
545
|
raise ValueError(f"Font size must be positive, got {self.size}")
|
|
480
546
|
|
|
@@ -684,107 +750,6 @@ class Image:
|
|
|
684
750
|
self.position = position
|
|
685
751
|
|
|
686
752
|
|
|
687
|
-
@dataclass
|
|
688
|
-
class TextLine:
|
|
689
|
-
"""
|
|
690
|
-
One line of text to add to a page.
|
|
691
|
-
|
|
692
|
-
Parameters:
|
|
693
|
-
- position: Anchor position where the first line begins.
|
|
694
|
-
- text: the text
|
|
695
|
-
provide separate entries for multiple lines.
|
|
696
|
-
- font: Font to use for all text elements unless overridden later.
|
|
697
|
-
- color: Text color.
|
|
698
|
-
|
|
699
|
-
"""
|
|
700
|
-
|
|
701
|
-
position: Optional[Position] = None
|
|
702
|
-
font: Optional[Font] = None
|
|
703
|
-
color: Optional[Color] = None
|
|
704
|
-
line_spacing: float = 1.2
|
|
705
|
-
text: str = ""
|
|
706
|
-
|
|
707
|
-
def get_position(self) -> Optional[Position]:
|
|
708
|
-
"""Returns the position of this paragraph."""
|
|
709
|
-
return self.position
|
|
710
|
-
|
|
711
|
-
def set_position(self, position: Position) -> None:
|
|
712
|
-
"""Sets the position of this paragraph."""
|
|
713
|
-
self.position = position
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
@dataclass
|
|
717
|
-
class Paragraph:
|
|
718
|
-
"""
|
|
719
|
-
Multi-line text paragraph to add to a page.
|
|
720
|
-
|
|
721
|
-
Parameters:
|
|
722
|
-
- position: Anchor position where the first line begins.
|
|
723
|
-
- text_lines: List of strings, one per line. Use `\n` within a string only if desired; normally
|
|
724
|
-
provide separate entries for multiple lines.
|
|
725
|
-
- font: Font to use for all text elements unless overridden later.
|
|
726
|
-
- color: Text color.
|
|
727
|
-
- line_spacing: Distance multiplier between lines. Server expects a list, handled for you by `AddRequest`.
|
|
728
|
-
|
|
729
|
-
Example:
|
|
730
|
-
```python
|
|
731
|
-
from pdfdancer.models import Paragraph, Position, Font, Color, StandardFonts, AddRequest
|
|
732
|
-
|
|
733
|
-
para = Paragraph(
|
|
734
|
-
position=Position.at_page_coordinates(0, 72, 700),
|
|
735
|
-
text_lines=["Hello", "PDFDancer!"],
|
|
736
|
-
font=Font(StandardFonts.HELVETICA.value, 12),
|
|
737
|
-
color=Color(50, 50, 50),
|
|
738
|
-
line_spacing=1.4,
|
|
739
|
-
)
|
|
740
|
-
payload = AddRequest(para).to_dict()
|
|
741
|
-
```
|
|
742
|
-
"""
|
|
743
|
-
|
|
744
|
-
position: Optional[Position] = None
|
|
745
|
-
text_lines: Optional[List[TextLine]] = None
|
|
746
|
-
font: Optional[Font] = None
|
|
747
|
-
color: Optional[Color] = None
|
|
748
|
-
line_spacing: float = 1.2
|
|
749
|
-
line_spacings: Optional[List[float]] = None
|
|
750
|
-
|
|
751
|
-
def get_position(self) -> Optional[Position]:
|
|
752
|
-
"""Returns the position of this paragraph."""
|
|
753
|
-
return self.position
|
|
754
|
-
|
|
755
|
-
def set_position(self, position: Position) -> None:
|
|
756
|
-
"""Sets the position of this paragraph."""
|
|
757
|
-
self.position = position
|
|
758
|
-
|
|
759
|
-
def clear_lines(self) -> None:
|
|
760
|
-
"""Removes all text lines from this paragraph."""
|
|
761
|
-
self.text_lines = []
|
|
762
|
-
|
|
763
|
-
def add_line(self, text_line: TextLine) -> None:
|
|
764
|
-
"""Appends a text line to this paragraph."""
|
|
765
|
-
if self.text_lines is None:
|
|
766
|
-
self.text_lines = []
|
|
767
|
-
self.text_lines.append(text_line)
|
|
768
|
-
|
|
769
|
-
def get_lines(self) -> List[TextLine]:
|
|
770
|
-
"""Returns the list of text lines, defaulting to an empty list."""
|
|
771
|
-
if self.text_lines is None:
|
|
772
|
-
self.text_lines = []
|
|
773
|
-
return self.text_lines
|
|
774
|
-
|
|
775
|
-
def set_lines(self, lines: List[TextLine]) -> None:
|
|
776
|
-
"""Replaces the current text lines with the provided list."""
|
|
777
|
-
self.text_lines = list(lines)
|
|
778
|
-
|
|
779
|
-
def set_line_spacings(self, spacings: Optional[List[float]]) -> None:
|
|
780
|
-
"""Sets the per-line spacing factors for this paragraph."""
|
|
781
|
-
self.line_spacings = list(spacings) if spacings else None
|
|
782
|
-
|
|
783
|
-
def get_line_spacings(self) -> Optional[List[float]]:
|
|
784
|
-
"""Returns the per-line spacing factors if present."""
|
|
785
|
-
return list(self.line_spacings) if self.line_spacings else None
|
|
786
|
-
|
|
787
|
-
|
|
788
753
|
# Request classes for API communication
|
|
789
754
|
@dataclass
|
|
790
755
|
class FindRequest:
|
|
@@ -799,7 +764,7 @@ class FindRequest:
|
|
|
799
764
|
```python
|
|
800
765
|
req = FindRequest(
|
|
801
766
|
object_type=ObjectType.TEXT_LINE,
|
|
802
|
-
position=Position.at_page_coordinates(
|
|
767
|
+
position=Position.at_page_coordinates(1, 72, 700).with_text_starts("Hello"),
|
|
803
768
|
)
|
|
804
769
|
payload = req.to_dict()
|
|
805
770
|
```
|
|
@@ -809,7 +774,7 @@ class FindRequest:
|
|
|
809
774
|
position: Optional[Position]
|
|
810
775
|
hint: Optional[str] = None
|
|
811
776
|
|
|
812
|
-
def to_dict(self) ->
|
|
777
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
813
778
|
"""Convert to dictionary for JSON serialization."""
|
|
814
779
|
return {
|
|
815
780
|
"objectType": self.object_type.value if self.object_type else None,
|
|
@@ -820,9 +785,9 @@ class FindRequest:
|
|
|
820
785
|
}
|
|
821
786
|
|
|
822
787
|
@staticmethod
|
|
823
|
-
def _position_to_dict(position: Position) ->
|
|
788
|
+
def _position_to_dict(position: Position) -> Dict[str, Any]:
|
|
824
789
|
"""Convert Position to dictionary for JSON serialization."""
|
|
825
|
-
result = {
|
|
790
|
+
result: Dict[str, Any] = {
|
|
826
791
|
"pageNumber": position.page_number,
|
|
827
792
|
"textStartsWith": position.text_starts_with,
|
|
828
793
|
"textPattern": position.text_pattern,
|
|
@@ -858,7 +823,7 @@ class DeleteRequest:
|
|
|
858
823
|
|
|
859
824
|
object_ref: ObjectRef
|
|
860
825
|
|
|
861
|
-
def to_dict(self) ->
|
|
826
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
862
827
|
"""Convert to dictionary for JSON serialization."""
|
|
863
828
|
# Use ObjectRef.to_dict() to ensure proper type normalization
|
|
864
829
|
return {"objectRef": self.object_ref.to_dict()}
|
|
@@ -883,7 +848,7 @@ class MoveRequest:
|
|
|
883
848
|
object_ref: ObjectRef
|
|
884
849
|
position: Position
|
|
885
850
|
|
|
886
|
-
def to_dict(self) ->
|
|
851
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
887
852
|
"""Convert to dictionary for JSON serialization."""
|
|
888
853
|
# Server API expects the new coordinates under 'newPosition'
|
|
889
854
|
# Use ObjectRef.to_dict() to ensure proper type normalization
|
|
@@ -893,55 +858,6 @@ class MoveRequest:
|
|
|
893
858
|
}
|
|
894
859
|
|
|
895
860
|
|
|
896
|
-
@dataclass
|
|
897
|
-
class RedactTarget:
|
|
898
|
-
"""A single redaction target identifying an object by its internal ID."""
|
|
899
|
-
|
|
900
|
-
id: str
|
|
901
|
-
replacement: str
|
|
902
|
-
|
|
903
|
-
def to_dict(self) -> dict:
|
|
904
|
-
return {"id": self.id, "replacement": self.replacement}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
@dataclass
|
|
908
|
-
class RedactRequest:
|
|
909
|
-
"""Request for redacting content from a PDF document."""
|
|
910
|
-
|
|
911
|
-
targets: List["RedactTarget"]
|
|
912
|
-
default_replacement: str
|
|
913
|
-
placeholder_color: "Color"
|
|
914
|
-
|
|
915
|
-
def to_dict(self) -> dict:
|
|
916
|
-
return {
|
|
917
|
-
"targets": [t.to_dict() for t in self.targets],
|
|
918
|
-
"defaultReplacement": self.default_replacement,
|
|
919
|
-
"placeholderColor": {
|
|
920
|
-
"r": self.placeholder_color.r,
|
|
921
|
-
"g": self.placeholder_color.g,
|
|
922
|
-
"b": self.placeholder_color.b,
|
|
923
|
-
"a": self.placeholder_color.a,
|
|
924
|
-
},
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
@dataclass
|
|
929
|
-
class RedactResponse:
|
|
930
|
-
"""Response from a redaction operation."""
|
|
931
|
-
|
|
932
|
-
count: int
|
|
933
|
-
success: bool
|
|
934
|
-
warnings: List[str]
|
|
935
|
-
|
|
936
|
-
@classmethod
|
|
937
|
-
def from_dict(cls, data: dict) -> "RedactResponse":
|
|
938
|
-
return cls(
|
|
939
|
-
count=data.get("count", 0),
|
|
940
|
-
success=data.get("success", False),
|
|
941
|
-
warnings=data.get("warnings", []),
|
|
942
|
-
)
|
|
943
|
-
|
|
944
|
-
|
|
945
861
|
@dataclass
|
|
946
862
|
class PageMoveRequest:
|
|
947
863
|
"""Request to reorder pages.
|
|
@@ -961,7 +877,7 @@ class PageMoveRequest:
|
|
|
961
877
|
from_page: int
|
|
962
878
|
to_page: int
|
|
963
879
|
|
|
964
|
-
def to_dict(self) ->
|
|
880
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
965
881
|
return {
|
|
966
882
|
"fromPage": self.from_page,
|
|
967
883
|
"toPage": self.to_page,
|
|
@@ -985,7 +901,7 @@ class AddPageRequest:
|
|
|
985
901
|
orientation: Optional[Orientation] = None
|
|
986
902
|
page_size: Optional[PageSize] = None
|
|
987
903
|
|
|
988
|
-
def to_dict(self) ->
|
|
904
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
989
905
|
payload: Dict[str, Any] = {}
|
|
990
906
|
if self.page_number is not None:
|
|
991
907
|
payload["pageNumber"] = int(self.page_number)
|
|
@@ -1012,23 +928,16 @@ class AddRequest:
|
|
|
1012
928
|
"""Request to add a new object to the document.
|
|
1013
929
|
|
|
1014
930
|
Parameters:
|
|
1015
|
-
- pdf_object: The object to add (
|
|
1016
|
-
|
|
1017
|
-
Usage:
|
|
1018
|
-
```python
|
|
1019
|
-
para = Paragraph(position=Position.at_page_coordinates(0, 72, 700), text_lines=["Hello"])
|
|
1020
|
-
req = AddRequest(para)
|
|
1021
|
-
payload = req.to_dict() # ready to send to the server API
|
|
1022
|
-
```
|
|
931
|
+
- pdf_object: The object to add (`Image` or `Path`).
|
|
1023
932
|
|
|
1024
933
|
Notes:
|
|
1025
934
|
- Serialization details (like base64 for image `data`, or per-segment position for paths)
|
|
1026
935
|
are handled for you in `to_dict()`.
|
|
1027
936
|
"""
|
|
1028
937
|
|
|
1029
|
-
pdf_object: Any
|
|
938
|
+
pdf_object: Any
|
|
1030
939
|
|
|
1031
|
-
def to_dict(self) ->
|
|
940
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
1032
941
|
"""Convert to dictionary for JSON serialization matching server API.
|
|
1033
942
|
Server expects an AddRequest with a nested 'object' containing the PDFObject
|
|
1034
943
|
(with a 'type' discriminator).
|
|
@@ -1036,7 +945,7 @@ class AddRequest:
|
|
|
1036
945
|
obj = self.pdf_object
|
|
1037
946
|
return {"object": self._object_to_dict(obj)}
|
|
1038
947
|
|
|
1039
|
-
def _object_to_dict(self, obj: Any) ->
|
|
948
|
+
def _object_to_dict(self, obj: Any) -> Dict[str, Any]:
|
|
1040
949
|
"""Convert PDF object to dictionary for JSON serialization."""
|
|
1041
950
|
import base64
|
|
1042
951
|
|
|
@@ -1084,128 +993,14 @@ class AddRequest:
|
|
|
1084
993
|
"size": size,
|
|
1085
994
|
"data": data_b64,
|
|
1086
995
|
}
|
|
1087
|
-
elif isinstance(obj, Paragraph):
|
|
1088
|
-
|
|
1089
|
-
def _font_to_dict(font: Optional[Font]) -> Optional[dict]:
|
|
1090
|
-
if font:
|
|
1091
|
-
return {"name": font.name, "size": font.size}
|
|
1092
|
-
return None
|
|
1093
|
-
|
|
1094
|
-
def _color_to_dict(color: Optional[Color]) -> Optional[dict]:
|
|
1095
|
-
if color:
|
|
1096
|
-
return {
|
|
1097
|
-
"red": color.r,
|
|
1098
|
-
"green": color.g,
|
|
1099
|
-
"blue": color.b,
|
|
1100
|
-
"alpha": color.a,
|
|
1101
|
-
}
|
|
1102
|
-
return None
|
|
1103
|
-
|
|
1104
|
-
lines_payload = []
|
|
1105
|
-
if obj.text_lines:
|
|
1106
|
-
for line in obj.text_lines:
|
|
1107
|
-
if isinstance(line, TextLine):
|
|
1108
|
-
line_text = line.text
|
|
1109
|
-
line_font = line.font or obj.font
|
|
1110
|
-
line_color = line.color or obj.color
|
|
1111
|
-
line_position = line.position or obj.position
|
|
1112
|
-
else:
|
|
1113
|
-
line_text = str(line)
|
|
1114
|
-
line_font = obj.font
|
|
1115
|
-
line_color = obj.color
|
|
1116
|
-
line_position = obj.position
|
|
1117
|
-
|
|
1118
|
-
text_element = {
|
|
1119
|
-
"text": line_text,
|
|
1120
|
-
"font": _font_to_dict(line_font),
|
|
1121
|
-
"color": _color_to_dict(line_color),
|
|
1122
|
-
"position": (
|
|
1123
|
-
FindRequest._position_to_dict(line_position)
|
|
1124
|
-
if line_position
|
|
1125
|
-
else None
|
|
1126
|
-
),
|
|
1127
|
-
}
|
|
1128
|
-
text_line = {"textElements": [text_element]}
|
|
1129
|
-
if line_color:
|
|
1130
|
-
text_line["color"] = _color_to_dict(line_color)
|
|
1131
|
-
if line_position:
|
|
1132
|
-
text_line["position"] = FindRequest._position_to_dict(
|
|
1133
|
-
line_position
|
|
1134
|
-
)
|
|
1135
|
-
lines_payload.append(text_line)
|
|
1136
|
-
|
|
1137
|
-
line_spacings = None
|
|
1138
|
-
if getattr(obj, "line_spacings", None):
|
|
1139
|
-
line_spacings = list(obj.line_spacings)
|
|
1140
|
-
elif getattr(obj, "line_spacing", None) is not None:
|
|
1141
|
-
line_spacings = [obj.line_spacing]
|
|
1142
|
-
|
|
1143
|
-
return {
|
|
1144
|
-
"type": "PARAGRAPH",
|
|
1145
|
-
"position": (
|
|
1146
|
-
FindRequest._position_to_dict(obj.position)
|
|
1147
|
-
if obj.position
|
|
1148
|
-
else None
|
|
1149
|
-
),
|
|
1150
|
-
"lines": lines_payload if lines_payload else None,
|
|
1151
|
-
"lineSpacings": line_spacings,
|
|
1152
|
-
"font": _font_to_dict(obj.font),
|
|
1153
|
-
}
|
|
1154
|
-
elif isinstance(obj, TextLine):
|
|
1155
|
-
|
|
1156
|
-
def _font_to_dict(font: Optional[Font]) -> Optional[dict]:
|
|
1157
|
-
if font:
|
|
1158
|
-
return {"name": font.name, "size": font.size}
|
|
1159
|
-
return None
|
|
1160
|
-
|
|
1161
|
-
def _color_to_dict(color: Optional[Color]) -> Optional[dict]:
|
|
1162
|
-
if color:
|
|
1163
|
-
return {
|
|
1164
|
-
"red": color.r,
|
|
1165
|
-
"green": color.g,
|
|
1166
|
-
"blue": color.b,
|
|
1167
|
-
"alpha": color.a,
|
|
1168
|
-
}
|
|
1169
|
-
return None
|
|
1170
|
-
|
|
1171
|
-
# Build textElement with only non-null fields
|
|
1172
|
-
text_element = {
|
|
1173
|
-
"text": obj.text,
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
if obj.font:
|
|
1177
|
-
text_element["font"] = _font_to_dict(obj.font)
|
|
1178
|
-
if obj.color:
|
|
1179
|
-
text_element["color"] = _color_to_dict(obj.color)
|
|
1180
|
-
if obj.position:
|
|
1181
|
-
text_element["position"] = FindRequest._position_to_dict(obj.position)
|
|
1182
|
-
|
|
1183
|
-
# TEXT_LINE structure matches paragraph line format (textElements only)
|
|
1184
|
-
result = {
|
|
1185
|
-
"type": "TEXT_LINE",
|
|
1186
|
-
"position": (
|
|
1187
|
-
FindRequest._position_to_dict(obj.position)
|
|
1188
|
-
if obj.position
|
|
1189
|
-
else None
|
|
1190
|
-
),
|
|
1191
|
-
"textElements": [text_element],
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
# Only include top-level font/color if they are not None
|
|
1195
|
-
if obj.font:
|
|
1196
|
-
result["font"] = _font_to_dict(obj.font)
|
|
1197
|
-
if obj.color:
|
|
1198
|
-
result["color"] = _color_to_dict(obj.color)
|
|
1199
|
-
|
|
1200
|
-
return result
|
|
1201
996
|
else:
|
|
1202
997
|
raise ValueError(f"Unsupported object type: {type(obj)}")
|
|
1203
998
|
|
|
1204
|
-
def _segment_to_dict(self, segment: "PathSegment") ->
|
|
999
|
+
def _segment_to_dict(self, segment: "PathSegment") -> Dict[str, Any]:
|
|
1205
1000
|
"""Convert a PathSegment (Line or Bezier) to dictionary for JSON serialization."""
|
|
1206
1001
|
from .models import Bezier, Line
|
|
1207
1002
|
|
|
1208
|
-
result = {}
|
|
1003
|
+
result: Dict[str, Any] = {}
|
|
1209
1004
|
|
|
1210
1005
|
# Add common PathSegment properties
|
|
1211
1006
|
if segment.stroke_color:
|
|
@@ -1263,20 +1058,13 @@ class ModifyRequest:
|
|
|
1263
1058
|
|
|
1264
1059
|
Parameters:
|
|
1265
1060
|
- object_ref: The existing object to replace.
|
|
1266
|
-
- new_object: The replacement object (
|
|
1267
|
-
|
|
1268
|
-
Example:
|
|
1269
|
-
```python
|
|
1270
|
-
new_para = Paragraph(position=old.position, text_lines=["Updated text"])
|
|
1271
|
-
req = ModifyRequest(object_ref=old, new_object=new_para)
|
|
1272
|
-
payload = req.to_dict()
|
|
1273
|
-
```
|
|
1061
|
+
- new_object: The replacement object (`Image` or `Path`).
|
|
1274
1062
|
"""
|
|
1275
1063
|
|
|
1276
1064
|
object_ref: ObjectRef
|
|
1277
1065
|
new_object: Any
|
|
1278
1066
|
|
|
1279
|
-
def to_dict(self) ->
|
|
1067
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
1280
1068
|
"""Convert to dictionary for JSON serialization."""
|
|
1281
1069
|
# Use ObjectRef.to_dict() to ensure proper type normalization
|
|
1282
1070
|
return {
|
|
@@ -1285,30 +1073,6 @@ class ModifyRequest:
|
|
|
1285
1073
|
}
|
|
1286
1074
|
|
|
1287
1075
|
|
|
1288
|
-
@dataclass
|
|
1289
|
-
class ModifyTextRequest:
|
|
1290
|
-
"""Request to change the text content of a text object.
|
|
1291
|
-
|
|
1292
|
-
Parameters:
|
|
1293
|
-
- object_ref: The text object to modify (e.g. a `TextObjectRef`).
|
|
1294
|
-
- new_text: Replacement text content.
|
|
1295
|
-
|
|
1296
|
-
Example:
|
|
1297
|
-
```python
|
|
1298
|
-
req = ModifyTextRequest(object_ref=text_ref, new_text="Hello world")
|
|
1299
|
-
payload = req.to_dict()
|
|
1300
|
-
```
|
|
1301
|
-
"""
|
|
1302
|
-
|
|
1303
|
-
object_ref: ObjectRef
|
|
1304
|
-
new_text: str
|
|
1305
|
-
|
|
1306
|
-
def to_dict(self) -> dict:
|
|
1307
|
-
"""Convert to dictionary for JSON serialization."""
|
|
1308
|
-
# Use ObjectRef.to_dict() to ensure proper type normalization
|
|
1309
|
-
return {"ref": self.object_ref.to_dict(), "newTextLine": self.new_text}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
1076
|
@dataclass
|
|
1313
1077
|
class ChangeFormFieldRequest:
|
|
1314
1078
|
"""Request to set a form field's value.
|
|
@@ -1328,7 +1092,7 @@ class ChangeFormFieldRequest:
|
|
|
1328
1092
|
object_ref: ObjectRef
|
|
1329
1093
|
value: str
|
|
1330
1094
|
|
|
1331
|
-
def to_dict(self) ->
|
|
1095
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
1332
1096
|
"""Convert to dictionary for JSON serialization."""
|
|
1333
1097
|
# Use ObjectRef.to_dict() to ensure proper type normalization
|
|
1334
1098
|
return {"ref": self.object_ref.to_dict(), "value": self.value}
|
|
@@ -1342,7 +1106,7 @@ class FormFieldRef(ObjectRef):
|
|
|
1342
1106
|
Parameters (usually provided by the server):
|
|
1343
1107
|
- internal_id: Identifier of the form field object.
|
|
1344
1108
|
- position: Position of the field.
|
|
1345
|
-
- type: One of `ObjectType.TEXT_FIELD`, `ObjectType.
|
|
1109
|
+
- type: One of `ObjectType.TEXT_FIELD`, `ObjectType.CHECKBOX`, etc.
|
|
1346
1110
|
- name: Field name (as defined inside the PDF).
|
|
1347
1111
|
- value: Current field value (string representation).
|
|
1348
1112
|
|
|
@@ -1401,7 +1165,7 @@ class TextStatus:
|
|
|
1401
1165
|
modified: bool
|
|
1402
1166
|
encodable: bool
|
|
1403
1167
|
font_type: FontType
|
|
1404
|
-
font_recommendation: FontRecommendation
|
|
1168
|
+
font_recommendation: Optional[FontRecommendation]
|
|
1405
1169
|
|
|
1406
1170
|
def is_modified(self) -> bool:
|
|
1407
1171
|
"""Check if the text has been modified."""
|
|
@@ -1415,7 +1179,7 @@ class TextStatus:
|
|
|
1415
1179
|
"""Get the font type."""
|
|
1416
1180
|
return self.font_type
|
|
1417
1181
|
|
|
1418
|
-
def get_font_recommendation(self) -> FontRecommendation:
|
|
1182
|
+
def get_font_recommendation(self) -> Optional[FontRecommendation]:
|
|
1419
1183
|
"""Get the font recommendation."""
|
|
1420
1184
|
return self.font_recommendation
|
|
1421
1185
|
|
|
@@ -1438,7 +1202,7 @@ class TextObjectRef(ObjectRef):
|
|
|
1438
1202
|
Usage:
|
|
1439
1203
|
- Instances are returned by find/snapshot APIs. You generally should not instantiate
|
|
1440
1204
|
them manually, but you may read their properties or pass their `ObjectRef`-like
|
|
1441
|
-
identity to
|
|
1205
|
+
identity to generic object operations.
|
|
1442
1206
|
"""
|
|
1443
1207
|
|
|
1444
1208
|
def __init__(
|
|
@@ -1548,14 +1312,14 @@ class CommandResult:
|
|
|
1548
1312
|
warning: str | None
|
|
1549
1313
|
|
|
1550
1314
|
@classmethod
|
|
1551
|
-
def from_dict(cls, data:
|
|
1315
|
+
def from_dict(cls, data: Dict[str, Any]) -> "CommandResult":
|
|
1552
1316
|
"""Create a CommandResult from a dictionary response."""
|
|
1553
1317
|
return cls(
|
|
1554
1318
|
command_name=data.get("commandName", ""),
|
|
1555
|
-
element_id=data.get("elementId"
|
|
1556
|
-
message=data.get("message"
|
|
1319
|
+
element_id=data.get("elementId"),
|
|
1320
|
+
message=data.get("message"),
|
|
1557
1321
|
success=data.get("success", False),
|
|
1558
|
-
warning=data.get("warning"
|
|
1322
|
+
warning=data.get("warning"),
|
|
1559
1323
|
)
|
|
1560
1324
|
|
|
1561
1325
|
@classmethod
|
|
@@ -1639,7 +1403,7 @@ class Size:
|
|
|
1639
1403
|
width: float
|
|
1640
1404
|
height: float
|
|
1641
1405
|
|
|
1642
|
-
def to_dict(self) ->
|
|
1406
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
1643
1407
|
return {"width": self.width, "height": self.height}
|
|
1644
1408
|
|
|
1645
1409
|
|
|
@@ -1682,113 +1446,6 @@ class ImageFlipDirection(Enum):
|
|
|
1682
1446
|
BOTH = "BOTH"
|
|
1683
1447
|
|
|
1684
1448
|
|
|
1685
|
-
class ReflowPreset(Enum):
|
|
1686
|
-
"""Reflow preset for template replacement operations.
|
|
1687
|
-
|
|
1688
|
-
Controls how text reflow is handled when replacement text differs in length
|
|
1689
|
-
from the original placeholder.
|
|
1690
|
-
|
|
1691
|
-
Values:
|
|
1692
|
-
- BEST_EFFORT: Attempt to reflow text, but proceed even if it doesn't fit perfectly.
|
|
1693
|
-
- FIT_OR_FAIL: Reflow must succeed or the operation fails.
|
|
1694
|
-
- NONE: No reflow - replacement text is placed as-is.
|
|
1695
|
-
"""
|
|
1696
|
-
|
|
1697
|
-
BEST_EFFORT = "BEST_EFFORT"
|
|
1698
|
-
FIT_OR_FAIL = "FIT_OR_FAIL"
|
|
1699
|
-
NONE = "NONE"
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
@dataclass
|
|
1703
|
-
class TemplateReplacement:
|
|
1704
|
-
"""A single template placeholder replacement.
|
|
1705
|
-
|
|
1706
|
-
Parameters:
|
|
1707
|
-
- placeholder: The exact text to find and replace in the PDF.
|
|
1708
|
-
- text: The text to replace the placeholder with. None for image replacements.
|
|
1709
|
-
- font: Optional font for the replacement text.
|
|
1710
|
-
- color: Optional color for the replacement text.
|
|
1711
|
-
- image: Optional Image to replace the placeholder with. When set, text should be None.
|
|
1712
|
-
"""
|
|
1713
|
-
|
|
1714
|
-
placeholder: str
|
|
1715
|
-
text: Optional[str] = None
|
|
1716
|
-
font: Optional[Font] = None
|
|
1717
|
-
color: Optional[Color] = None
|
|
1718
|
-
image: Optional[Image] = None
|
|
1719
|
-
|
|
1720
|
-
def to_dict(self) -> dict:
|
|
1721
|
-
"""Convert to dictionary for JSON serialization."""
|
|
1722
|
-
import base64
|
|
1723
|
-
|
|
1724
|
-
result: Dict[str, Any] = {
|
|
1725
|
-
"placeholder": self.placeholder,
|
|
1726
|
-
"text": self.text,
|
|
1727
|
-
}
|
|
1728
|
-
if self.font:
|
|
1729
|
-
result["font"] = {"name": self.font.name, "size": self.font.size}
|
|
1730
|
-
if self.color:
|
|
1731
|
-
result["color"] = {
|
|
1732
|
-
"red": self.color.r,
|
|
1733
|
-
"green": self.color.g,
|
|
1734
|
-
"blue": self.color.b,
|
|
1735
|
-
"alpha": self.color.a,
|
|
1736
|
-
}
|
|
1737
|
-
if self.image:
|
|
1738
|
-
image_dict: Dict[str, Any] = {}
|
|
1739
|
-
if self.image.data:
|
|
1740
|
-
image_dict["data"] = base64.b64encode(self.image.data).decode("utf-8")
|
|
1741
|
-
if self.image.format:
|
|
1742
|
-
image_dict["format"] = self.image.format
|
|
1743
|
-
if self.image.width is not None or self.image.height is not None:
|
|
1744
|
-
size: Dict[str, float] = {}
|
|
1745
|
-
if self.image.width is not None:
|
|
1746
|
-
size["width"] = self.image.width
|
|
1747
|
-
if self.image.height is not None:
|
|
1748
|
-
size["height"] = self.image.height
|
|
1749
|
-
image_dict["size"] = size
|
|
1750
|
-
result["image"] = image_dict
|
|
1751
|
-
return result
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
@dataclass
|
|
1755
|
-
class TemplateReplaceRequest:
|
|
1756
|
-
"""Request for batch template placeholder replacements.
|
|
1757
|
-
|
|
1758
|
-
Parameters:
|
|
1759
|
-
- replacements: List of TemplateReplacement objects.
|
|
1760
|
-
- page_index: Optional 0-based page index. If None, applies to all pages.
|
|
1761
|
-
- reflow_preset: Optional ReflowPreset for text reflow behavior.
|
|
1762
|
-
|
|
1763
|
-
Example:
|
|
1764
|
-
```python
|
|
1765
|
-
request = TemplateReplaceRequest(
|
|
1766
|
-
replacements=[
|
|
1767
|
-
TemplateReplacement("{{NAME}}", "John Doe"),
|
|
1768
|
-
TemplateReplacement("{{DATE}}", "2025-01-15"),
|
|
1769
|
-
],
|
|
1770
|
-
page_index=0, # First page (0-based)
|
|
1771
|
-
reflow_preset=ReflowPreset.BEST_EFFORT,
|
|
1772
|
-
)
|
|
1773
|
-
```
|
|
1774
|
-
"""
|
|
1775
|
-
|
|
1776
|
-
replacements: List[TemplateReplacement]
|
|
1777
|
-
page_index: Optional[int] = None
|
|
1778
|
-
reflow_preset: Optional[ReflowPreset] = None
|
|
1779
|
-
|
|
1780
|
-
def to_dict(self) -> dict:
|
|
1781
|
-
"""Convert to dictionary for JSON serialization."""
|
|
1782
|
-
result: Dict[str, Any] = {
|
|
1783
|
-
"replacements": [r.to_dict() for r in self.replacements],
|
|
1784
|
-
}
|
|
1785
|
-
if self.page_index is not None:
|
|
1786
|
-
result["pageIndex"] = self.page_index
|
|
1787
|
-
if self.reflow_preset is not None:
|
|
1788
|
-
result["reflowPreset"] = self.reflow_preset.value
|
|
1789
|
-
return result
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
1449
|
@dataclass
|
|
1793
1450
|
class ImageTransformRequest:
|
|
1794
1451
|
"""Request to transform an image in the PDF document.
|
|
@@ -1825,7 +1482,7 @@ class ImageTransformRequest:
|
|
|
1825
1482
|
fill_region_height: Optional[int] = None
|
|
1826
1483
|
fill_color: Optional[int] = None
|
|
1827
1484
|
|
|
1828
|
-
def to_dict(self) ->
|
|
1485
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
1829
1486
|
"""Convert to dictionary for JSON serialization."""
|
|
1830
1487
|
import base64
|
|
1831
1488
|
|
|
@@ -1917,7 +1574,7 @@ class ModifyPathRequest:
|
|
|
1917
1574
|
stroke_color: Optional[Color] = None
|
|
1918
1575
|
fill_color: Optional[Color] = None
|
|
1919
1576
|
|
|
1920
|
-
def to_dict(self) ->
|
|
1577
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
1921
1578
|
"""Convert to dictionary for JSON serialization."""
|
|
1922
1579
|
result: Dict[str, Any] = {
|
|
1923
1580
|
"ref": self.object_ref.to_dict(),
|