pdfdancer-client-python 0.2.2__tar.gz → 0.2.4__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.
Potentially problematic release.
This version of pdfdancer-client-python might be problematic. Click here for more details.
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/.gitignore +1 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/CLAUDE.md +1 -1
- {pdfdancer_client_python-0.2.2/src/pdfdancer_client_python.egg-info → pdfdancer_client_python-0.2.4}/PKG-INFO +14 -13
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/README.md +13 -12
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/pyproject.toml +1 -1
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer/pdfdancer_v1.py +53 -4
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4/src/pdfdancer_client_python.egg-info}/PKG-INFO +14 -13
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer_client_python.egg-info/SOURCES.txt +1 -1
- pdfdancer_client_python-0.2.4/tests/test_authentication.py +36 -0
- pdfdancer_client_python-0.2.2/test.py +0 -455
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/.github/workflows/ci.yml +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/docs/openapi.yml +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/release.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/requirements-dev.txt +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/requirements.txt +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/setup.cfg +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer/__init__.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer/exceptions.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer/image_builder.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer/models.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer/paragraph_builder.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer/types.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/__init__.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/e2e/__init__.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/e2e/test_acroform.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/e2e/test_form_x_objects.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/e2e/test_image.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/e2e/test_line.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/e2e/test_page.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/e2e/test_paragraph.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/e2e/test_path.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/fixtures/DancingScript-Regular.ttf +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/fixtures/ObviouslyAwesome.pdf +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/fixtures/basic-paths.pdf +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/fixtures/form-xobject-example.pdf +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/fixtures/logo-80.png +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/fixtures/mixed-form-types.pdf +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/test_models.py +0 -0
- {pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/test_openapi_compliance.py +0 -0
|
@@ -46,7 +46,7 @@ client._delete(paragraphs[0])
|
|
|
46
46
|
client._move(images[0], new_position)
|
|
47
47
|
|
|
48
48
|
# Builder pattern (mirrors Java ParagraphBuilder)
|
|
49
|
-
paragraph = (client.
|
|
49
|
+
paragraph = (client._paragraph_builder()
|
|
50
50
|
.from_string("Text content")
|
|
51
51
|
.with_font(Font("Arial", 12))
|
|
52
52
|
.with_position(Position.at_page_coordinates(0, 100, 200))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdfdancer-client-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Python client for PDFDancer API
|
|
5
5
|
Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
|
|
6
6
|
License: MIT
|
|
@@ -63,7 +63,7 @@ client._delete(paragraphs[0])
|
|
|
63
63
|
client._move(images[0], Position.at_page_coordinates(0, 100, 200))
|
|
64
64
|
|
|
65
65
|
# Builder pattern (mirrors Java ParagraphBuilder)
|
|
66
|
-
paragraph = (client.
|
|
66
|
+
paragraph = (client._paragraph_builder()
|
|
67
67
|
.from_string("Hello World")
|
|
68
68
|
.with_font(Font("Arial", 12))
|
|
69
69
|
.with_color(Color(255, 0, 0))
|
|
@@ -141,25 +141,26 @@ result = client.modify_text_line(ref, "new text")
|
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
### Builder Pattern
|
|
144
|
+
|
|
144
145
|
```python
|
|
145
146
|
# Java: client.paragraphBuilder()
|
|
146
|
-
builder = client.
|
|
147
|
+
builder = client._paragraph_builder()
|
|
147
148
|
|
|
148
149
|
# Fluent interface (mirrors Java ParagraphBuilder)
|
|
149
150
|
paragraph = (builder
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
.from_string("Text content") # Java: fromString()
|
|
152
|
+
.with_font(Font("Arial", 12)) # Java: withFont()
|
|
153
|
+
.with_color(Color(255, 0, 0)) # Java: withColor()
|
|
154
|
+
.with_line_spacing(1.5) # Java: withLineSpacing()
|
|
155
|
+
.with_position(position) # Java: withPosition()
|
|
156
|
+
.build()) # Java: build()
|
|
156
157
|
|
|
157
158
|
# Font file registration (Java: withFont(File, double))
|
|
158
159
|
paragraph = (builder
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
.with_font_file("custom.ttf", 14.0) # Java: withFont(File, double)
|
|
161
|
+
.from_string("Custom font text")
|
|
162
|
+
.with_position(position)
|
|
163
|
+
.build())
|
|
163
164
|
```
|
|
164
165
|
|
|
165
166
|
### Position API
|
|
@@ -37,7 +37,7 @@ client._delete(paragraphs[0])
|
|
|
37
37
|
client._move(images[0], Position.at_page_coordinates(0, 100, 200))
|
|
38
38
|
|
|
39
39
|
# Builder pattern (mirrors Java ParagraphBuilder)
|
|
40
|
-
paragraph = (client.
|
|
40
|
+
paragraph = (client._paragraph_builder()
|
|
41
41
|
.from_string("Hello World")
|
|
42
42
|
.with_font(Font("Arial", 12))
|
|
43
43
|
.with_color(Color(255, 0, 0))
|
|
@@ -115,25 +115,26 @@ result = client.modify_text_line(ref, "new text")
|
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
### Builder Pattern
|
|
118
|
+
|
|
118
119
|
```python
|
|
119
120
|
# Java: client.paragraphBuilder()
|
|
120
|
-
builder = client.
|
|
121
|
+
builder = client._paragraph_builder()
|
|
121
122
|
|
|
122
123
|
# Fluent interface (mirrors Java ParagraphBuilder)
|
|
123
124
|
paragraph = (builder
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
.from_string("Text content") # Java: fromString()
|
|
126
|
+
.with_font(Font("Arial", 12)) # Java: withFont()
|
|
127
|
+
.with_color(Color(255, 0, 0)) # Java: withColor()
|
|
128
|
+
.with_line_spacing(1.5) # Java: withLineSpacing()
|
|
129
|
+
.with_position(position) # Java: withPosition()
|
|
130
|
+
.build()) # Java: build()
|
|
130
131
|
|
|
131
132
|
# Font file registration (Java: withFont(File, double))
|
|
132
133
|
paragraph = (builder
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
.with_font_file("custom.ttf", 14.0) # Java: withFont(File, double)
|
|
135
|
+
.from_string("Custom font text")
|
|
136
|
+
.with_position(position)
|
|
137
|
+
.build())
|
|
137
138
|
```
|
|
138
139
|
|
|
139
140
|
### Position API
|
{pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer/pdfdancer_v1.py
RENAMED
|
@@ -6,6 +6,7 @@ Provides session-based PDF manipulation operations with strict validation.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
|
+
import os
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import List, Optional, Union, BinaryIO
|
|
11
12
|
|
|
@@ -116,11 +117,39 @@ class PDFDancer:
|
|
|
116
117
|
@classmethod
|
|
117
118
|
def open(cls,
|
|
118
119
|
pdf_data: Union[bytes, Path, str, BinaryIO],
|
|
119
|
-
token: str,
|
|
120
|
-
base_url: str =
|
|
120
|
+
token: Optional[str] = None,
|
|
121
|
+
base_url: Optional[str] = None,
|
|
121
122
|
timeout: float = 30.0) -> "PDFDancer":
|
|
123
|
+
"""
|
|
124
|
+
Create a client session, falling back to environment variables when needed.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
pdf_data: PDF payload supplied directly or via filesystem handles.
|
|
128
|
+
token: Override for the API token; falls back to `PDFDANCER_TOKEN` environement variable.
|
|
129
|
+
base_url: Override for the API base URL; falls back to `PDFDANCER_BASE_URL`
|
|
130
|
+
or defaults to `https://api.pdfdancer.com`.
|
|
131
|
+
timeout: HTTP read timeout in seconds.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
A ready-to-use `PDFDancer` client instance.
|
|
135
|
+
"""
|
|
136
|
+
resolved_token = token.strip() if token and token.strip() else None
|
|
137
|
+
if resolved_token is None:
|
|
138
|
+
env_token = os.getenv("PDFDANCER_TOKEN")
|
|
139
|
+
resolved_token = env_token.strip() if env_token and env_token.strip() else None
|
|
140
|
+
|
|
141
|
+
if resolved_token is None:
|
|
142
|
+
raise ValidationException(
|
|
143
|
+
"Missing PDFDancer API token. Pass a token via the `token` argument "
|
|
144
|
+
"or set the PDFDANCER_TOKEN environment variable."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
env_base_url = os.getenv("PDFDANCER_BASE_URL")
|
|
148
|
+
resolved_base_url = base_url or (env_base_url.strip() if env_base_url and env_base_url.strip() else None)
|
|
149
|
+
if resolved_base_url is None:
|
|
150
|
+
resolved_base_url = "https://api.pdfdancer.com"
|
|
122
151
|
|
|
123
|
-
return PDFDancer(
|
|
152
|
+
return PDFDancer(resolved_token, pdf_data, resolved_base_url, timeout)
|
|
124
153
|
|
|
125
154
|
def __init__(self, token: str, pdf_data: Union[bytes, Path, str, BinaryIO],
|
|
126
155
|
base_url: str, read_timeout: float = 0):
|
|
@@ -236,6 +265,22 @@ class PDFDancer:
|
|
|
236
265
|
# If JSON parsing fails, return response content or status
|
|
237
266
|
return response.text or f"HTTP {response.status_code}"
|
|
238
267
|
|
|
268
|
+
def _handle_authentication_error(self, response: Optional[requests.Response]) -> None:
|
|
269
|
+
"""
|
|
270
|
+
Translate authentication failures into a clear, actionable validation error.
|
|
271
|
+
"""
|
|
272
|
+
if response is None:
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
if response.status_code in (401, 403):
|
|
276
|
+
details = self._extract_error_message(response)
|
|
277
|
+
raise ValidationException(
|
|
278
|
+
"Authentication with the PDFDancer API failed. "
|
|
279
|
+
"Confirm that your API token is valid, has not expired, and is supplied via "
|
|
280
|
+
"the `token` argument or the PDFDANCER_TOKEN environment variable. "
|
|
281
|
+
f"Server response: {details}"
|
|
282
|
+
)
|
|
283
|
+
|
|
239
284
|
def _create_session(self) -> str:
|
|
240
285
|
"""
|
|
241
286
|
Creates a new PDF processing session by uploading the PDF data.
|
|
@@ -251,6 +296,7 @@ class PDFDancer:
|
|
|
251
296
|
timeout=self._read_timeout if self._read_timeout > 0 else None
|
|
252
297
|
)
|
|
253
298
|
|
|
299
|
+
self._handle_authentication_error(response)
|
|
254
300
|
response.raise_for_status()
|
|
255
301
|
session_id = response.text.strip()
|
|
256
302
|
|
|
@@ -260,6 +306,7 @@ class PDFDancer:
|
|
|
260
306
|
return session_id
|
|
261
307
|
|
|
262
308
|
except requests.exceptions.RequestException as e:
|
|
309
|
+
self._handle_authentication_error(getattr(e, 'response', None))
|
|
263
310
|
error_message = self._extract_error_message(getattr(e, 'response', None))
|
|
264
311
|
raise HttpClientException(f"Failed to create session: {error_message}",
|
|
265
312
|
response=getattr(e, 'response', None), cause=e) from None
|
|
@@ -293,10 +340,12 @@ class PDFDancer:
|
|
|
293
340
|
except (json.JSONDecodeError, KeyError):
|
|
294
341
|
pass
|
|
295
342
|
|
|
343
|
+
self._handle_authentication_error(response)
|
|
296
344
|
response.raise_for_status()
|
|
297
345
|
return response
|
|
298
346
|
|
|
299
347
|
except requests.exceptions.RequestException as e:
|
|
348
|
+
self._handle_authentication_error(getattr(e, 'response', None))
|
|
300
349
|
error_message = self._extract_error_message(getattr(e, 'response', None))
|
|
301
350
|
raise HttpClientException(f"API request failed: {error_message}", response=getattr(e, 'response', None),
|
|
302
351
|
cause=e) from None
|
|
@@ -811,7 +860,7 @@ class PDFDancer:
|
|
|
811
860
|
|
|
812
861
|
# Builder Pattern Support
|
|
813
862
|
|
|
814
|
-
def
|
|
863
|
+
def _paragraph_builder(self) -> 'ParagraphBuilder':
|
|
815
864
|
"""
|
|
816
865
|
Creates a new ParagraphBuilder for fluent paragraph construction.
|
|
817
866
|
Returns:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdfdancer-client-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Python client for PDFDancer API
|
|
5
5
|
Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
|
|
6
6
|
License: MIT
|
|
@@ -63,7 +63,7 @@ client._delete(paragraphs[0])
|
|
|
63
63
|
client._move(images[0], Position.at_page_coordinates(0, 100, 200))
|
|
64
64
|
|
|
65
65
|
# Builder pattern (mirrors Java ParagraphBuilder)
|
|
66
|
-
paragraph = (client.
|
|
66
|
+
paragraph = (client._paragraph_builder()
|
|
67
67
|
.from_string("Hello World")
|
|
68
68
|
.with_font(Font("Arial", 12))
|
|
69
69
|
.with_color(Color(255, 0, 0))
|
|
@@ -141,25 +141,26 @@ result = client.modify_text_line(ref, "new text")
|
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
### Builder Pattern
|
|
144
|
+
|
|
144
145
|
```python
|
|
145
146
|
# Java: client.paragraphBuilder()
|
|
146
|
-
builder = client.
|
|
147
|
+
builder = client._paragraph_builder()
|
|
147
148
|
|
|
148
149
|
# Fluent interface (mirrors Java ParagraphBuilder)
|
|
149
150
|
paragraph = (builder
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
.from_string("Text content") # Java: fromString()
|
|
152
|
+
.with_font(Font("Arial", 12)) # Java: withFont()
|
|
153
|
+
.with_color(Color(255, 0, 0)) # Java: withColor()
|
|
154
|
+
.with_line_spacing(1.5) # Java: withLineSpacing()
|
|
155
|
+
.with_position(position) # Java: withPosition()
|
|
156
|
+
.build()) # Java: build()
|
|
156
157
|
|
|
157
158
|
# Font file registration (Java: withFont(File, double))
|
|
158
159
|
paragraph = (builder
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
.with_font_file("custom.ttf", 14.0) # Java: withFont(File, double)
|
|
161
|
+
.from_string("Custom font text")
|
|
162
|
+
.with_position(position)
|
|
163
|
+
.build())
|
|
163
164
|
```
|
|
164
165
|
|
|
165
166
|
### Position API
|
|
@@ -5,7 +5,6 @@ pyproject.toml
|
|
|
5
5
|
release.py
|
|
6
6
|
requirements-dev.txt
|
|
7
7
|
requirements.txt
|
|
8
|
-
test.py
|
|
9
8
|
.github/workflows/ci.yml
|
|
10
9
|
docs/openapi.yml
|
|
11
10
|
src/pdfdancer/__init__.py
|
|
@@ -21,6 +20,7 @@ src/pdfdancer_client_python.egg-info/dependency_links.txt
|
|
|
21
20
|
src/pdfdancer_client_python.egg-info/requires.txt
|
|
22
21
|
src/pdfdancer_client_python.egg-info/top_level.txt
|
|
23
22
|
tests/__init__.py
|
|
23
|
+
tests/test_authentication.py
|
|
24
24
|
tests/test_models.py
|
|
25
25
|
tests/test_openapi_compliance.py
|
|
26
26
|
tests/e2e/__init__.py
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from pdfdancer import ValidationException
|
|
5
|
+
from pdfdancer.pdfdancer_v1 import PDFDancer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_open_without_token_reports_actionable_message():
|
|
9
|
+
with pytest.raises(ValidationException) as exc_info:
|
|
10
|
+
PDFDancer.open(pdf_data=b"%PDF", token="")
|
|
11
|
+
|
|
12
|
+
message = str(exc_info.value)
|
|
13
|
+
assert "Missing PDFDancer API token" in message
|
|
14
|
+
assert "PDFDANCER_TOKEN" in message
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_create_session_unauthorized_reports_guidance(monkeypatch):
|
|
18
|
+
class FakeResponse:
|
|
19
|
+
status_code = 401
|
|
20
|
+
text = "Unauthorized"
|
|
21
|
+
|
|
22
|
+
def json(self):
|
|
23
|
+
return {"message": "Unauthorized"}
|
|
24
|
+
|
|
25
|
+
def fake_post(self, *args, **kwargs):
|
|
26
|
+
return FakeResponse()
|
|
27
|
+
|
|
28
|
+
monkeypatch.setattr(requests.Session, "post", fake_post)
|
|
29
|
+
|
|
30
|
+
with pytest.raises(ValidationException) as exc_info:
|
|
31
|
+
PDFDancer(token="bad-token", pdf_data=b"%PDF", base_url="https://api.example.com")
|
|
32
|
+
|
|
33
|
+
message = str(exc_info.value)
|
|
34
|
+
assert "Authentication with the PDFDancer API failed" in message
|
|
35
|
+
assert "Server response: Unauthorized" in message
|
|
36
|
+
assert "PDFDANCER_TOKEN" in message
|
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Comprehensive test showcasing all PDFDancer Python API features.
|
|
3
|
-
This file demonstrates the complete functionality of the pdfdancer library.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
from pdfdancer import (
|
|
9
|
-
ClientV1, Font, Position, Color, Image, Paragraph, ObjectType,
|
|
10
|
-
Point, BoundingRect, PositionMode, ShapeType,
|
|
11
|
-
FontNotFoundException
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# noinspection PyUnusedLocal
|
|
16
|
-
def find_operations(client):
|
|
17
|
-
"""Test all find operations for different object types."""
|
|
18
|
-
# Find all paragraphs
|
|
19
|
-
paragraphs = client._find_paragraphs()
|
|
20
|
-
|
|
21
|
-
# Find images
|
|
22
|
-
images = client._find_images()
|
|
23
|
-
|
|
24
|
-
# Find forms
|
|
25
|
-
forms = client._find_form_x_objects()
|
|
26
|
-
|
|
27
|
-
# Find paths
|
|
28
|
-
paths = client._find_paths()
|
|
29
|
-
|
|
30
|
-
# Find text lines
|
|
31
|
-
text_lines = client._find_text_lines()
|
|
32
|
-
|
|
33
|
-
# Find with specific object type and position
|
|
34
|
-
specific_position = Position.at_page_coordinates(0, 100, 100)
|
|
35
|
-
paragraphs_at_pos = client._find(ObjectType.PARAGRAPH, specific_position)
|
|
36
|
-
|
|
37
|
-
return paragraphs, text_lines # Keep only what's needed for object_manipulation
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
# noinspection PyUnusedLocal
|
|
41
|
-
def page_management(client):
|
|
42
|
-
"""Test page management operations."""
|
|
43
|
-
# Get all pages
|
|
44
|
-
pages = client.get_pages()
|
|
45
|
-
|
|
46
|
-
# Get specific page
|
|
47
|
-
first_page = None
|
|
48
|
-
if pages:
|
|
49
|
-
first_page = client._get_page(0)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
# noinspection PyUnusedLocal
|
|
53
|
-
def position_system(client):
|
|
54
|
-
"""Test position and coordinate system operations."""
|
|
55
|
-
# Different ways to create positions
|
|
56
|
-
page_pos = Position.at_page(0)
|
|
57
|
-
coord_pos = Position.at_page_coordinates(0, 50, 100)
|
|
58
|
-
|
|
59
|
-
# Position manipulation
|
|
60
|
-
moved_pos = coord_pos.copy()
|
|
61
|
-
moved_pos.move_x(25.0)
|
|
62
|
-
moved_pos.move_y(15.0)
|
|
63
|
-
|
|
64
|
-
# Point and BoundingRect
|
|
65
|
-
point = Point(10.0, 20.0)
|
|
66
|
-
bounding_rect = BoundingRect(0.0, 0.0, 100.0, 50.0)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# noinspection PyUnusedLocal
|
|
70
|
-
def font_management(client):
|
|
71
|
-
"""Test font creation and management operations."""
|
|
72
|
-
# Create fonts
|
|
73
|
-
arial_font = Font("Arial", 12.0)
|
|
74
|
-
times_font = Font("Times New Roman", 14.0)
|
|
75
|
-
|
|
76
|
-
# Find fonts in document
|
|
77
|
-
found_fonts = None
|
|
78
|
-
try:
|
|
79
|
-
found_fonts = client.find_fonts("Arial", 12)
|
|
80
|
-
except FontNotFoundException as e:
|
|
81
|
-
pass
|
|
82
|
-
except Exception as e:
|
|
83
|
-
pass
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# noinspection PyUnusedLocal
|
|
87
|
-
def color_system(client):
|
|
88
|
-
"""Test color creation and management operations."""
|
|
89
|
-
# Create colors
|
|
90
|
-
red_color = Color(255, 0, 0)
|
|
91
|
-
blue_color = Color(0, 0, 255, 128) # With alpha
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# noinspection PyUnusedLocal
|
|
95
|
-
def paragraph_operations(client):
|
|
96
|
-
"""Test paragraph builder and manipulation operations."""
|
|
97
|
-
arial_font = Font("Arial", 12.0)
|
|
98
|
-
arial_font_large = Font("Arial", 14.0) # Use Arial instead of Times New Roman
|
|
99
|
-
red_color = Color(255, 0, 0)
|
|
100
|
-
blue_color = Color(0, 0, 255, 128)
|
|
101
|
-
|
|
102
|
-
# Basic paragraph creation
|
|
103
|
-
basic_paragraph = (client.paragraph_builder()
|
|
104
|
-
.from_string("Basic paragraph with default settings")
|
|
105
|
-
.with_font(arial_font)
|
|
106
|
-
.with_position(Position.at_page_coordinates(0, 50, 200))
|
|
107
|
-
.build())
|
|
108
|
-
client._add_paragraph(basic_paragraph)
|
|
109
|
-
|
|
110
|
-
# Advanced paragraph with all features
|
|
111
|
-
advanced_paragraph = (client.paragraph_builder()
|
|
112
|
-
.from_string("Advanced paragraph\nwith multiple lines\nand styling", red_color)
|
|
113
|
-
.with_font(arial_font_large)
|
|
114
|
-
.with_line_spacing(1.5)
|
|
115
|
-
.with_color(blue_color)
|
|
116
|
-
.with_position(Position.at_page_coordinates(0, 50, 250))
|
|
117
|
-
.build())
|
|
118
|
-
client._add_paragraph(advanced_paragraph)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# noinspection PyUnusedLocal
|
|
122
|
-
def object_manipulation(client, paragraphs, text_lines):
|
|
123
|
-
"""Test object manipulation operations including delete, move, and modify."""
|
|
124
|
-
arial_font = Font("Arial", 14.0) # Use Arial instead of Times New Roman
|
|
125
|
-
red_color = Color(255, 0, 0)
|
|
126
|
-
|
|
127
|
-
if paragraphs:
|
|
128
|
-
# Delete operation
|
|
129
|
-
first_paragraph = paragraphs[0]
|
|
130
|
-
client._delete(first_paragraph)
|
|
131
|
-
|
|
132
|
-
# Move operation (if we have more paragraphs)
|
|
133
|
-
if len(paragraphs) > 1:
|
|
134
|
-
second_paragraph = paragraphs[1]
|
|
135
|
-
new_position = Position.at_page_coordinates(0, 100, 300)
|
|
136
|
-
client._move(second_paragraph, new_position)
|
|
137
|
-
|
|
138
|
-
# Modify operations
|
|
139
|
-
if len(paragraphs) > 2:
|
|
140
|
-
third_paragraph = paragraphs[2]
|
|
141
|
-
|
|
142
|
-
# Modify with new paragraph object
|
|
143
|
-
modified_paragraph = Paragraph(
|
|
144
|
-
position=Position.at_page_coordinates(0, 50, 350),
|
|
145
|
-
text_lines=["Modified paragraph text"],
|
|
146
|
-
font=arial_font,
|
|
147
|
-
color=red_color
|
|
148
|
-
)
|
|
149
|
-
client._modify_paragraph(third_paragraph, modified_paragraph)
|
|
150
|
-
|
|
151
|
-
# Modify with just text
|
|
152
|
-
if len(paragraphs) > 3:
|
|
153
|
-
fourth_paragraph = paragraphs[3]
|
|
154
|
-
client._modify_paragraph(fourth_paragraph, "Simple text modification")
|
|
155
|
-
|
|
156
|
-
# Text line modification
|
|
157
|
-
if text_lines:
|
|
158
|
-
client.modify_text_line(text_lines[0], "Modified text line")
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
# noinspection PyUnusedLocal
|
|
162
|
-
def text_line_operations(client):
|
|
163
|
-
"""Test text line specific operations and showcases."""
|
|
164
|
-
# Find all text lines in document
|
|
165
|
-
all_text_lines = client._find_text_lines()
|
|
166
|
-
|
|
167
|
-
# Find text lines at specific position
|
|
168
|
-
specific_position = Position.at_page_coordinates(0, 100, 100)
|
|
169
|
-
text_lines_at_position = client._find_text_lines(specific_position)
|
|
170
|
-
|
|
171
|
-
# Find text lines on entire page
|
|
172
|
-
page_position = Position.at_page(0)
|
|
173
|
-
text_lines_on_page = client._find_text_lines(page_position)
|
|
174
|
-
|
|
175
|
-
# Demonstrate text line modification if text lines are found
|
|
176
|
-
modified_text_line = None
|
|
177
|
-
if all_text_lines:
|
|
178
|
-
first_text_line = all_text_lines[0]
|
|
179
|
-
|
|
180
|
-
# Get text line properties via ObjectRef
|
|
181
|
-
text_line_id = first_text_line.get_internal_id()
|
|
182
|
-
text_line_type = first_text_line.get_type()
|
|
183
|
-
text_line_position = first_text_line.get_position()
|
|
184
|
-
|
|
185
|
-
# Modify text line content
|
|
186
|
-
client.modify_text_line(first_text_line, "Modified text line content")
|
|
187
|
-
modified_text_line = first_text_line
|
|
188
|
-
|
|
189
|
-
# Demonstrate position modification for text lines
|
|
190
|
-
if len(all_text_lines) > 1:
|
|
191
|
-
second_text_line = all_text_lines[1]
|
|
192
|
-
new_position = Position.at_page_coordinates(0, 120, 150)
|
|
193
|
-
client._move(second_text_line, new_position)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
# noinspection PyUnusedLocal
|
|
197
|
-
def form_operations(client):
|
|
198
|
-
"""Test form handling operations."""
|
|
199
|
-
# Find all forms in document
|
|
200
|
-
all_forms = client._find_form_x_objects()
|
|
201
|
-
|
|
202
|
-
# Find forms at specific position
|
|
203
|
-
specific_position = Position.at_page_coordinates(0, 150, 200)
|
|
204
|
-
forms_at_position = client._find_form_x_objects(specific_position)
|
|
205
|
-
|
|
206
|
-
# Find forms on entire page
|
|
207
|
-
page_position = Position.at_page(0)
|
|
208
|
-
forms_on_page = client._find_form_x_objects(page_position)
|
|
209
|
-
|
|
210
|
-
# Demonstrate form manipulation if forms are found
|
|
211
|
-
manipulated_form = None
|
|
212
|
-
if all_forms:
|
|
213
|
-
first_form = all_forms[0]
|
|
214
|
-
|
|
215
|
-
# Get form properties via ObjectRef
|
|
216
|
-
form_id = first_form.get_internal_id()
|
|
217
|
-
form_type = first_form.get_type()
|
|
218
|
-
form_position = first_form.get_position()
|
|
219
|
-
|
|
220
|
-
# Demonstrate position modification for forms
|
|
221
|
-
if len(all_forms) > 1:
|
|
222
|
-
second_form = all_forms[1]
|
|
223
|
-
new_position = Position.at_page_coordinates(0, 180, 250)
|
|
224
|
-
client._move(second_form, new_position)
|
|
225
|
-
manipulated_form = second_form
|
|
226
|
-
|
|
227
|
-
# Demonstrate deletion (use last form to preserve others for testing)
|
|
228
|
-
if len(all_forms) > 2:
|
|
229
|
-
last_form = all_forms[-1]
|
|
230
|
-
client._delete(last_form)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
# noinspection PyUnusedLocal
|
|
234
|
-
def path_operations(client):
|
|
235
|
-
"""Test path handling operations."""
|
|
236
|
-
# Find all paths in document
|
|
237
|
-
all_paths = client._find_paths()
|
|
238
|
-
|
|
239
|
-
# Find paths at specific position
|
|
240
|
-
specific_position = Position.at_page_coordinates(0, 100, 150)
|
|
241
|
-
paths_at_position = client._find_paths(specific_position)
|
|
242
|
-
|
|
243
|
-
# Find paths on entire page
|
|
244
|
-
page_position = Position.at_page(0)
|
|
245
|
-
paths_on_page = client._find_paths(page_position)
|
|
246
|
-
|
|
247
|
-
# Demonstrate path manipulation if paths are found
|
|
248
|
-
manipulated_path = None
|
|
249
|
-
if all_paths:
|
|
250
|
-
first_path = all_paths[0]
|
|
251
|
-
|
|
252
|
-
# Get path properties via ObjectRef
|
|
253
|
-
path_id = first_path.get_internal_id()
|
|
254
|
-
path_type = first_path.get_type()
|
|
255
|
-
path_position = first_path.get_position()
|
|
256
|
-
|
|
257
|
-
# Demonstrate position modification for paths
|
|
258
|
-
if len(all_paths) > 1:
|
|
259
|
-
second_path = all_paths[1]
|
|
260
|
-
new_position = Position.at_page_coordinates(0, 120, 180)
|
|
261
|
-
client._move(second_path, new_position)
|
|
262
|
-
manipulated_path = second_path
|
|
263
|
-
|
|
264
|
-
# Demonstrate deletion (use last path to preserve others for testing)
|
|
265
|
-
if len(all_paths) > 2:
|
|
266
|
-
last_path = all_paths[-1]
|
|
267
|
-
client._delete(last_path)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
# noinspection PyUnusedLocal
|
|
271
|
-
def register_font_operations(client):
|
|
272
|
-
"""Test font registration operations using JetBrainsMono-Regular.ttf."""
|
|
273
|
-
# Register font using file path
|
|
274
|
-
font_path = Path("tests/fixtures/JetBrainsMono-Regular.ttf")
|
|
275
|
-
registered_font_name = client.register_font(font_path)
|
|
276
|
-
|
|
277
|
-
# Create a Font object using the registered font
|
|
278
|
-
jetbrains_font = Font(registered_font_name, 14.0)
|
|
279
|
-
|
|
280
|
-
# Use the registered font in a paragraph
|
|
281
|
-
paragraph_with_custom_font = (client.paragraph_builder()
|
|
282
|
-
.from_string("This text uses JetBrains Mono font")
|
|
283
|
-
.with_font(jetbrains_font)
|
|
284
|
-
.with_position(Position.at_page_coordinates(0, 50, 450))
|
|
285
|
-
.build())
|
|
286
|
-
client._add_paragraph(paragraph_with_custom_font)
|
|
287
|
-
|
|
288
|
-
# Register font using bytes (alternative method)
|
|
289
|
-
with open(font_path, 'rb') as f:
|
|
290
|
-
font_bytes = f.read()
|
|
291
|
-
registered_font_name_bytes = client.register_font(font_bytes)
|
|
292
|
-
|
|
293
|
-
# Create another Font object using the bytes-registered font
|
|
294
|
-
jetbrains_font_bytes = Font(registered_font_name_bytes, 16.0)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
# noinspection PyUnusedLocal
|
|
298
|
-
def image_operations(client):
|
|
299
|
-
"""Test image handling operations using logo-80.png."""
|
|
300
|
-
# Load actual image file
|
|
301
|
-
image_path = Path("tests/fixtures/logo-80.png")
|
|
302
|
-
with open(image_path, 'rb') as f:
|
|
303
|
-
image_bytes = f.read()
|
|
304
|
-
|
|
305
|
-
# Create image object with real image data
|
|
306
|
-
logo_image = Image(
|
|
307
|
-
position=Position.at_page_coordinates(0, 200, 400),
|
|
308
|
-
format="image/png",
|
|
309
|
-
width=80.0,
|
|
310
|
-
height=80.0,
|
|
311
|
-
data=image_bytes
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
# Add image to PDF
|
|
315
|
-
client._add_image(logo_image)
|
|
316
|
-
|
|
317
|
-
# Find images in document
|
|
318
|
-
found_images = client._find_images()
|
|
319
|
-
|
|
320
|
-
# Demonstrate image manipulation if images are found
|
|
321
|
-
if found_images:
|
|
322
|
-
first_image = found_images[0]
|
|
323
|
-
|
|
324
|
-
# Get image properties via ObjectRef
|
|
325
|
-
image_id = first_image.get_internal_id()
|
|
326
|
-
image_type = first_image.get_type()
|
|
327
|
-
image_position = first_image.get_position()
|
|
328
|
-
|
|
329
|
-
# Move image to a new position
|
|
330
|
-
new_image_position = Position.at_page_coordinates(0, 250, 450)
|
|
331
|
-
client._move(first_image, new_image_position)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
# noinspection PyUnusedLocal
|
|
335
|
-
def objectref_operations(client, paragraphs):
|
|
336
|
-
"""Test ObjectRef operations."""
|
|
337
|
-
if paragraphs and len(paragraphs) > 1: # Use remaining paragraphs after deletion
|
|
338
|
-
obj_ref = paragraphs[1]
|
|
339
|
-
internal_id = obj_ref.get_internal_id()
|
|
340
|
-
obj_type = obj_ref.get_type()
|
|
341
|
-
position = obj_ref.get_position()
|
|
342
|
-
obj_dict = obj_ref.to_dict()
|
|
343
|
-
|
|
344
|
-
# Set new position
|
|
345
|
-
new_ref_position = Position.at_page_coordinates(0, 75, 125)
|
|
346
|
-
obj_ref.set_position(new_ref_position)
|
|
347
|
-
updated_position = obj_ref.get_position()
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
# noinspection PyUnusedLocal
|
|
351
|
-
def enumerations(client):
|
|
352
|
-
"""Test enumeration demonstrations."""
|
|
353
|
-
object_types = list(ObjectType)
|
|
354
|
-
position_modes = list(PositionMode)
|
|
355
|
-
shape_types = list(ShapeType)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
# noinspection PyUnusedLocal
|
|
359
|
-
def error_handling(client):
|
|
360
|
-
"""Test error handling demonstration."""
|
|
361
|
-
exception_caught = None
|
|
362
|
-
try:
|
|
363
|
-
# This might trigger a font not found error
|
|
364
|
-
client.find_fonts("NonExistentFont", 12)
|
|
365
|
-
except FontNotFoundException as e:
|
|
366
|
-
exception_caught = e
|
|
367
|
-
except Exception as e:
|
|
368
|
-
exception_caught = e
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
# noinspection PyUnusedLocal
|
|
372
|
-
def pdf_operations(client):
|
|
373
|
-
"""Test PDF operations."""
|
|
374
|
-
# Get PDF data
|
|
375
|
-
pdf_data = client.get_pdf_file()
|
|
376
|
-
|
|
377
|
-
# Save PDF (original functionality + comprehensive output)
|
|
378
|
-
output_path = "comprehensive_output.pdf"
|
|
379
|
-
client.save_pdf(output_path)
|
|
380
|
-
client.save_pdf("output.pdf") # Keep original output file
|
|
381
|
-
|
|
382
|
-
# Verify file exists
|
|
383
|
-
file_exists = Path(output_path).exists()
|
|
384
|
-
file_size = Path(output_path).stat().st_size if file_exists else 0
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
def context_management():
|
|
388
|
-
"""Test context manager capabilities."""
|
|
389
|
-
with open("jwt-token-mlahr-20250829-160417.txt", "r", encoding="utf-8") as f:
|
|
390
|
-
token = f.read()
|
|
391
|
-
|
|
392
|
-
# Context manager automatically handles session lifecycle
|
|
393
|
-
with ClientV1(token=token, pdf_data="tests/fixtures/ObviouslyAwesome.pdf") as client:
|
|
394
|
-
paragraphs = client.find_paragraphs()
|
|
395
|
-
|
|
396
|
-
# Builder pattern works seamlessly inside context (original functionality preserved)
|
|
397
|
-
paragraph = (client.paragraph_builder()
|
|
398
|
-
.from_string("Context managed")
|
|
399
|
-
.with_font(Font("Arial", 12))
|
|
400
|
-
.with_position(Position.at_page_coordinates(0, 50, 10))
|
|
401
|
-
.build())
|
|
402
|
-
|
|
403
|
-
client._add_paragraph(paragraph)
|
|
404
|
-
|
|
405
|
-
return len(paragraphs)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
def advanced_positioning():
|
|
409
|
-
"""Test advanced positioning features."""
|
|
410
|
-
# Create positions using different methods
|
|
411
|
-
positions = [
|
|
412
|
-
Position.at_page(0),
|
|
413
|
-
Position.at_page_coordinates(1, 100, 200),
|
|
414
|
-
Position.at_page_coordinates(2, 150, 250)
|
|
415
|
-
]
|
|
416
|
-
|
|
417
|
-
modified_positions = []
|
|
418
|
-
for pos in positions:
|
|
419
|
-
# Demonstrate position copying and modification
|
|
420
|
-
copied_pos = pos.copy()
|
|
421
|
-
copied_pos.move_x(25).move_y(35)
|
|
422
|
-
modified_positions.append(copied_pos)
|
|
423
|
-
|
|
424
|
-
return positions, modified_positions
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
def main():
|
|
428
|
-
"""Main function demonstrating all API features."""
|
|
429
|
-
with open("jwt-token-mlahr-20250829-160417.txt", "r", encoding="utf-8") as f:
|
|
430
|
-
token = f.read()
|
|
431
|
-
|
|
432
|
-
with ClientV1(token=token, pdf_data="tests/fixtures/ObviouslyAwesome.pdf", read_timeout=30.0) as client:
|
|
433
|
-
# Execute all test operations
|
|
434
|
-
paragraphs, text_lines = find_operations(client)
|
|
435
|
-
page_management(client)
|
|
436
|
-
position_system(client)
|
|
437
|
-
font_management(client)
|
|
438
|
-
register_font_operations(client)
|
|
439
|
-
color_system(client)
|
|
440
|
-
paragraph_operations(client)
|
|
441
|
-
object_manipulation(client, paragraphs, text_lines)
|
|
442
|
-
text_line_operations(client)
|
|
443
|
-
form_operations(client)
|
|
444
|
-
path_operations(client)
|
|
445
|
-
image_operations(client)
|
|
446
|
-
objectref_operations(client, paragraphs)
|
|
447
|
-
enumerations(client)
|
|
448
|
-
error_handling(client)
|
|
449
|
-
pdf_operations(client)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
if __name__ == "__main__":
|
|
453
|
-
main()
|
|
454
|
-
context_management()
|
|
455
|
-
advanced_positioning()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer/image_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/src/pdfdancer/paragraph_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/e2e/test_form_x_objects.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/fixtures/ObviouslyAwesome.pdf
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/fixtures/basic-paths.pdf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/fixtures/mixed-form-types.pdf
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.2 → pdfdancer_client_python-0.2.4}/tests/test_openapi_compliance.py
RENAMED
|
File without changes
|