pdfdancer-client-python 0.3.1__py3-none-any.whl → 0.3.2__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 CHANGED
@@ -35,6 +35,8 @@ from .models import (
35
35
  Point,
36
36
  Position,
37
37
  PositionMode,
38
+ RedactResponse,
39
+ RedactTarget,
38
40
  ShapeType,
39
41
  StandardFonts,
40
42
  TextObjectRef,
@@ -78,6 +80,8 @@ __all__ = [
78
80
  "Line",
79
81
  "Bezier",
80
82
  "Path",
83
+ "RedactTarget",
84
+ "RedactResponse",
81
85
  "PdfDancerException",
82
86
  "FontNotFoundException",
83
87
  "ValidationException",
pdfdancer/models.py CHANGED
@@ -893,6 +893,55 @@ class MoveRequest:
893
893
  }
894
894
 
895
895
 
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
+
896
945
  @dataclass
897
946
  class PageMoveRequest:
898
947
  """Request to reorder pages.
pdfdancer/pdfdancer_v1.py CHANGED
@@ -14,6 +14,7 @@ import os
14
14
  import sys
15
15
  import time
16
16
  from datetime import datetime, timezone
17
+ from importlib.metadata import version as get_package_version
17
18
  from pathlib import Path
18
19
  from typing import TYPE_CHECKING, Any, BinaryIO, List, Mapping, Optional, Union
19
20
 
@@ -35,6 +36,7 @@ from .models import (
35
36
  AddPageRequest,
36
37
  AddRequest,
37
38
  ChangeFormFieldRequest,
39
+ Color,
38
40
  CommandResult,
39
41
  DeleteRequest,
40
42
  DocumentSnapshot,
@@ -57,6 +59,9 @@ from .models import (
57
59
  Paragraph,
58
60
  Position,
59
61
  PositionMode,
62
+ RedactRequest,
63
+ RedactResponse,
64
+ RedactTarget,
60
65
  ShapeType,
61
66
  TextLine,
62
67
  TextObjectRef,
@@ -69,6 +74,7 @@ from .types import (
69
74
  ImageObject,
70
75
  ParagraphObject,
71
76
  PathObject,
77
+ PDFObjectBase,
72
78
  TextLineObject,
73
79
  )
74
80
 
@@ -78,6 +84,14 @@ if TYPE_CHECKING:
78
84
 
79
85
  load_dotenv()
80
86
 
87
+ # Client identifier header for all HTTP requests
88
+ # Always reads from installed package metadata (stays in sync with pyproject.toml)
89
+ try:
90
+ CLIENT_HEADER_VALUE = f"python/{get_package_version('pdfdancer-client-python')}"
91
+ except Exception:
92
+ # Fallback in case package metadata is not available
93
+ CLIENT_HEADER_VALUE = "python/unknown"
94
+
81
95
  # Global variable to disable SSL certificate verification
82
96
  # Set to True to skip SSL verification (useful for testing with self-signed certificates)
83
97
  # WARNING: Only use in development/testing environments
@@ -748,7 +762,10 @@ class PDFDancer:
748
762
 
749
763
  while attempt <= max_retries:
750
764
  try:
751
- headers = {"X-Fingerprint": Fingerprint.generate()}
765
+ headers = {
766
+ "X-Fingerprint": Fingerprint.generate(),
767
+ "X-PDFDancer-Client": CLIENT_HEADER_VALUE,
768
+ }
752
769
 
753
770
  response = temp_client.post(
754
771
  cls._cleanup_url_path(base_url, "/keys/anon"),
@@ -910,7 +927,10 @@ class PDFDancer:
910
927
  # Create HTTP client for connection reuse with HTTP/2 support
911
928
  instance._client = httpx.Client(
912
929
  http2=True,
913
- headers={"Authorization": f"Bearer {instance._token}"},
930
+ headers={
931
+ "Authorization": f"Bearer {instance._token}",
932
+ "X-PDFDancer-Client": CLIENT_HEADER_VALUE,
933
+ },
914
934
  verify=not DISABLE_SSL_VERIFY,
915
935
  )
916
936
 
@@ -976,7 +996,10 @@ class PDFDancer:
976
996
  # Create HTTP client for connection reuse with HTTP/2 support
977
997
  self._client = httpx.Client(
978
998
  http2=True,
979
- headers={"Authorization": f"Bearer {self._token}"},
999
+ headers={
1000
+ "Authorization": f"Bearer {self._token}",
1001
+ "X-PDFDancer-Client": CLIENT_HEADER_VALUE,
1002
+ },
980
1003
  verify=not DISABLE_SSL_VERIFY,
981
1004
  )
982
1005
 
@@ -2054,6 +2077,58 @@ class PDFDancer:
2054
2077
 
2055
2078
  return result
2056
2079
 
2080
+ def _redact(
2081
+ self,
2082
+ targets: List["RedactTarget"],
2083
+ default_replacement: str = "[REDACTED]",
2084
+ placeholder_color: Optional[Color] = None,
2085
+ ) -> "RedactResponse":
2086
+ """
2087
+ Redacts specified objects from the PDF document.
2088
+
2089
+ Args:
2090
+ targets: List of RedactTarget objects identifying what to redact
2091
+ default_replacement: Default replacement text for redacted content
2092
+ placeholder_color: Color for image/path placeholder rectangles
2093
+
2094
+ Returns:
2095
+ RedactResponse with count, success status, and any warnings
2096
+ """
2097
+ if not targets:
2098
+ raise ValidationException("At least one redaction target is required")
2099
+
2100
+ if placeholder_color is None:
2101
+ placeholder_color = Color(0, 0, 0)
2102
+
2103
+ request = RedactRequest(targets, default_replacement, placeholder_color)
2104
+ response = self._make_request("POST", "/pdf/redact", data=request.to_dict())
2105
+ result = RedactResponse.from_dict(response.json())
2106
+
2107
+ if result.success:
2108
+ self._invalidate_snapshots()
2109
+
2110
+ return result
2111
+
2112
+ def redact(
2113
+ self,
2114
+ objects: List["PDFObjectBase"],
2115
+ replacement: str = "[REDACTED]",
2116
+ placeholder_color: Optional[Color] = None,
2117
+ ) -> "RedactResponse":
2118
+ """
2119
+ Redacts multiple objects from the PDF document.
2120
+
2121
+ Args:
2122
+ objects: List of PDF objects to redact
2123
+ replacement: Replacement text for all redacted content
2124
+ placeholder_color: Color for image/path placeholder rectangles
2125
+
2126
+ Returns:
2127
+ RedactResponse with count, success status, and any warnings
2128
+ """
2129
+ targets = [RedactTarget(obj.internal_id, replacement) for obj in objects]
2130
+ return self._redact(targets, replacement, placeholder_color)
2131
+
2057
2132
  # Add Operations
2058
2133
 
2059
2134
  def _add_image(self, image: Image, position: Optional[Position] = None) -> bool:
pdfdancer/types.py CHANGED
@@ -64,6 +64,14 @@ class PDFObjectBase:
64
64
  Position.at_page_coordinates(self.position.page_number, x, y),
65
65
  )
66
66
 
67
+ def redact(self, replacement: str = "[REDACTED]") -> bool:
68
+ """Redact this object from the PDF document."""
69
+ from .models import RedactTarget
70
+
71
+ target = RedactTarget(self.internal_id, replacement)
72
+ result = self._client._redact([target], replacement)
73
+ return result.success
74
+
67
75
 
68
76
  # -------------------------------------------------------------------
69
77
  # Subclasses
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfdancer-client-python
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Python client for PDFDancer API
5
5
  Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
6
6
  License:
@@ -300,7 +300,7 @@ with PDFDancer.open(
300
300
  .font(StandardFonts.HELVETICA, 12) \
301
301
  .color(Color(70, 70, 70)) \
302
302
  .line_spacing(1.4) \
303
- .at(page_number=0, x=72, y=520) \
303
+ .at(page_number=1, x=72, y=520) \
304
304
  .add()
305
305
 
306
306
  # Persist the modified document
@@ -321,7 +321,7 @@ with PDFDancer.new(token="your-api-token") as pdf:
321
321
  .font(StandardFonts.TIMES_BOLD, 18) \
322
322
  .color(Color(10, 10, 80)) \
323
323
  .line_spacing(1.2) \
324
- .at(page_number=0, x=72, y=730) \
324
+ .at(page_number=1, x=72, y=730) \
325
325
  .add()
326
326
 
327
327
  pdf.new_image() \
@@ -1,17 +1,17 @@
1
- pdfdancer/__init__.py,sha256=DkDPiTq-u5FWGpD5FHFXpAvZ7em79_PWauQgtE5VLIw,2427
1
+ pdfdancer/__init__.py,sha256=m_buZKsE08oJSuU_ofWjN--IdPvnSiWrin6xwCYYL80,2507
2
2
  pdfdancer/exceptions.py,sha256=mJacmUJTPGUsB8Bo_FgfeXYkvZkH5OPJCVBfilBVmQo,2058
3
3
  pdfdancer/fingerprint.py,sha256=eL3PHPgv-knMya7s95RXg3qzzpkAA1aevxqb6tuOb34,3061
4
4
  pdfdancer/image_builder.py,sha256=aBxMFAMFzzbGTjlVH0hi94mA81cH8tp37Pk84HRPV00,1892
5
- pdfdancer/models.py,sha256=TzUe5EGG_EufkRLbJFWSKhpTBfwt5C4g-jPWP2djtwI,50630
5
+ pdfdancer/models.py,sha256=JKvncVzzkXJy47XIQP9Ydvl2xkbi4IPXO60weXPoBiw,51856
6
6
  pdfdancer/page_builder.py,sha256=ARWLRtlrLGbES-0nbiigTOsRVmodRX0DNK8YIAkA9Ig,3850
7
7
  pdfdancer/paragraph_builder.py,sha256=OmhzYazMnq0n0rhrjWcKbo0LQfEC7BZoiLB29ycF630,20504
8
8
  pdfdancer/path_builder.py,sha256=safKb_IeHRWlQbyBTIXfcoBfXxUZhuzYvBIob5Tbp-8,23938
9
- pdfdancer/pdfdancer_v1.py,sha256=3dsOeIglFS_azisQ8vfrTuhT9-09gZOvTRZ28Re-el8,120183
9
+ pdfdancer/pdfdancer_v1.py,sha256=aLAdyhvxxQrjKQxDHgoIrUxwcIgIAYS4twL6TOOeSQ0,122812
10
10
  pdfdancer/text_line_builder.py,sha256=8jYknV4hw0fyzwX0OI_oLvnh5aMmCV3jXaVkmYLu6MQ,10273
11
- pdfdancer/types.py,sha256=XX0pr-G34ghZz0wT_2A9ivHQAjtChHAEPlZYQj4X3z4,16344
12
- pdfdancer_client_python-0.3.1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
13
- pdfdancer_client_python-0.3.1.dist-info/licenses/NOTICE,sha256=xaC4l-IChAmtViNDie8ZWzUk0O6XRMyzOl0zLmVZ2HE,232
14
- pdfdancer_client_python-0.3.1.dist-info/METADATA,sha256=VY1nAJH871I0XmVt4i67JG3iZ81CwovcN0uwuwvLsp4,24736
15
- pdfdancer_client_python-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
- pdfdancer_client_python-0.3.1.dist-info/top_level.txt,sha256=ICwSVRpcCKrdBF9QlaX9Y0e_N3Nk1p7QVxadGOnbxeY,10
17
- pdfdancer_client_python-0.3.1.dist-info/RECORD,,
11
+ pdfdancer/types.py,sha256=gRpAOSmp9pESHjHdsrda8d2E6q8fGxZKVg59guxXg3g,16658
12
+ pdfdancer_client_python-0.3.2.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
13
+ pdfdancer_client_python-0.3.2.dist-info/licenses/NOTICE,sha256=xaC4l-IChAmtViNDie8ZWzUk0O6XRMyzOl0zLmVZ2HE,232
14
+ pdfdancer_client_python-0.3.2.dist-info/METADATA,sha256=VAUegFatdCjjiRZyldhJ5jP8gbq-2W3vTRqw-iOyoEs,24736
15
+ pdfdancer_client_python-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ pdfdancer_client_python-0.3.2.dist-info/top_level.txt,sha256=ICwSVRpcCKrdBF9QlaX9Y0e_N3Nk1p7QVxadGOnbxeY,10
17
+ pdfdancer_client_python-0.3.2.dist-info/RECORD,,