pdfdancer-client-python 0.3.12__tar.gz → 0.3.13__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.
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/.github/workflows/ci.yml +67 -28
- pdfdancer_client_python-0.3.13/.github/workflows/sdk-backward-compat.yml +88 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/PKG-INFO +1 -1
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/pyproject.toml +1 -1
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/__init__.py +5 -1
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/models.py +82 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/pdfdancer_v1.py +75 -3
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/types.py +92 -1
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer_client_python.egg-info/PKG-INFO +1 -1
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer_client_python.egg-info/SOURCES.txt +1 -1
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_context_manager.py +6 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path.py +48 -1
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_redact.py +1 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_pdf_object_equality.py +19 -10
- pdfdancer_client_python-0.3.12/.api-url +0 -1
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/.claude/commands/discuss.md +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/.claude/commands/implement-new-api-features.md +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/.flake8 +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/.github/workflows/daily-tests.yml +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/.github/workflows/release.yml +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/.gitignore +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/.gitmodules +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/CLAUDE.md +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/LICENSE +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/NOTICE +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/README.md +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/TODO.md +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/check.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/docs/capabilities/.gitkeep +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/docs/capabilities/CLEAR_CLIPPING.md +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/media/logo-orange-512h.webp +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/media/logo-orange-60h.webp +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/media/logo-silver-512h.webp +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/media/logo-silver-60h.webp +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/release.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/setup.cfg +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/exceptions.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/fingerprint.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/image_builder.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/page_builder.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/paragraph_builder.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/path_builder.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/text_line_builder.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/test.sh +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/__init__.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/conftest.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/__init__.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/pdf_assertions.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_acroform.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_bezier_builder.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_clipping.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_form_x_objects.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_image.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_image_transform.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_line.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_line_builder.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_new_pdf.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_page.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_paragraph.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path_builder.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path_builder_rectangle.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path_comprehensive.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path_group.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_pdfdancer.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_positioning.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_rectangle_builder.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_singular_selection.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_snapshot.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_template_replace.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_template_replace_linebreak.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_text_line_edit.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/Asimovian-Regular.ttf +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/DancingScript-Regular.ttf +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/Empty.pdf +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/Roboto-Regular.ttf +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/Showcase.pdf +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/basic-paths.pdf +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/form-xobject-example.pdf +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/invisible-content-clipping-test.pdf +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/logo-80.png +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/mixed-form-types.pdf +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_anonymous_token.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_fingerprint.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_models.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_openapi_compliance.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_path_models.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_rate_limit.py +0 -0
- {pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_standard_fonts.py +0 -0
|
@@ -12,6 +12,10 @@ jobs:
|
|
|
12
12
|
# Quick minimal test on non-main branches
|
|
13
13
|
if: github.ref != 'refs/heads/main'
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
issues: read
|
|
18
|
+
pull-requests: read
|
|
15
19
|
strategy:
|
|
16
20
|
fail-fast: false
|
|
17
21
|
max-parallel: 2
|
|
@@ -23,16 +27,51 @@ jobs:
|
|
|
23
27
|
with:
|
|
24
28
|
submodules: true
|
|
25
29
|
|
|
26
|
-
- name:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
- name: Resolve API base URL
|
|
31
|
+
id: resolve-api-base-url
|
|
32
|
+
uses: actions/github-script@v7
|
|
33
|
+
with:
|
|
34
|
+
github-token: ${{ secrets.PROPAGATE_TOKEN }}
|
|
35
|
+
script: |
|
|
36
|
+
const fallbackUrl = 'https://api-staging.pdfdancer.com';
|
|
37
|
+
const pr = context.payload.pull_request;
|
|
38
|
+
if (!pr) {
|
|
39
|
+
core.setOutput('base_url', fallbackUrl);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const body = pr.body || '';
|
|
44
|
+
const apiPrMatch = body.match(/## Source API PR[\s\S]*?^- Repository:\s*MenschMachine\/pdfdancer-api\s*$[\s\S]*?^- PR:\s*#?(\d+)\s*$/im);
|
|
45
|
+
if (!apiPrMatch) {
|
|
46
|
+
core.info('No Source API PR block found in the PR body; using staging API.');
|
|
47
|
+
core.setOutput('base_url', fallbackUrl);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const apiPrNumber = Number(apiPrMatch[1]);
|
|
52
|
+
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
53
|
+
owner: 'MenschMachine',
|
|
54
|
+
repo: 'pdfdancer-api',
|
|
55
|
+
issue_number: apiPrNumber,
|
|
56
|
+
per_page: 100,
|
|
57
|
+
});
|
|
58
|
+
const previewComments = comments.filter((comment) =>
|
|
59
|
+
typeof comment.body === 'string' && comment.body.includes('### Preview Environment')
|
|
60
|
+
);
|
|
61
|
+
if (previewComments.length === 0) {
|
|
62
|
+
core.setFailed(`Source API PR #${apiPrNumber} is referenced by this PR, but no preview comment was found.`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const previewComment = previewComments[previewComments.length - 1];
|
|
67
|
+
const urlMatch = previewComment.body.match(/\*\*API URL:\*\*\s*(https?:\/\/\S+)/);
|
|
68
|
+
if (!urlMatch) {
|
|
69
|
+
core.setFailed(`Preview comment on source API PR #${apiPrNumber} does not contain a parseable API URL.`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
core.info(`Using preview API URL from source API PR #${apiPrNumber}: ${urlMatch[1]}`);
|
|
74
|
+
core.setOutput('base_url', urlMatch[1]);
|
|
36
75
|
|
|
37
76
|
- name: Set up Python ${{ matrix.python-version }}
|
|
38
77
|
uses: actions/setup-python@v5
|
|
@@ -53,8 +92,8 @@ jobs:
|
|
|
53
92
|
|
|
54
93
|
- name: Run tests
|
|
55
94
|
run: |
|
|
56
|
-
PDFDANCER_BASE_URL=${{ steps.api-url.outputs.base_url
|
|
57
|
-
|
|
95
|
+
PDFDANCER_BASE_URL=${{ steps.resolve-api-base-url.outputs.base_url }} \
|
|
96
|
+
PDFDANCER_API_TOKEN=42 \
|
|
58
97
|
venv/bin/python -m pytest tests/ -v --maxfail=3
|
|
59
98
|
|
|
60
99
|
- name: Build distribution packages
|
|
@@ -68,6 +107,10 @@ jobs:
|
|
|
68
107
|
# Full matrix only on main
|
|
69
108
|
if: github.ref == 'refs/heads/main'
|
|
70
109
|
runs-on: ${{ matrix.os }}
|
|
110
|
+
permissions:
|
|
111
|
+
contents: read
|
|
112
|
+
issues: read
|
|
113
|
+
pull-requests: read
|
|
71
114
|
strategy:
|
|
72
115
|
fail-fast: false
|
|
73
116
|
max-parallel: 2
|
|
@@ -80,17 +123,13 @@ jobs:
|
|
|
80
123
|
with:
|
|
81
124
|
submodules: true
|
|
82
125
|
|
|
83
|
-
- name:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if [ -n "$url" ]; then
|
|
91
|
-
echo "base_url=$url" >> "$GITHUB_OUTPUT"
|
|
92
|
-
fi
|
|
93
|
-
fi
|
|
126
|
+
- name: Resolve API base URL
|
|
127
|
+
id: resolve-api-base-url
|
|
128
|
+
uses: actions/github-script@v7
|
|
129
|
+
with:
|
|
130
|
+
github-token: ${{ secrets.PROPAGATE_TOKEN }}
|
|
131
|
+
script: |
|
|
132
|
+
core.setOutput('base_url', 'https://api-staging.pdfdancer.com');
|
|
94
133
|
|
|
95
134
|
- name: Set up Python ${{ matrix.python-version }}
|
|
96
135
|
uses: actions/setup-python@v5
|
|
@@ -115,8 +154,8 @@ jobs:
|
|
|
115
154
|
- name: Run tests (Unix)
|
|
116
155
|
if: runner.os != 'Windows'
|
|
117
156
|
run: |
|
|
118
|
-
PDFDANCER_BASE_URL=${{ steps.api-url.outputs.base_url
|
|
119
|
-
|
|
157
|
+
PDFDANCER_BASE_URL=${{ steps.resolve-api-base-url.outputs.base_url }} \
|
|
158
|
+
PDFDANCER_API_TOKEN=42 \
|
|
120
159
|
venv/bin/python -m pytest tests/ -v --maxfail=3
|
|
121
160
|
|
|
122
161
|
- name: Build & Validate (Unix)
|
|
@@ -146,8 +185,8 @@ jobs:
|
|
|
146
185
|
if: runner.os == 'Windows'
|
|
147
186
|
shell: cmd
|
|
148
187
|
run: |
|
|
149
|
-
set PDFDANCER_BASE_URL=${{ steps.api-url.outputs.base_url
|
|
150
|
-
set
|
|
188
|
+
set PDFDANCER_BASE_URL=${{ steps.resolve-api-base-url.outputs.base_url }}
|
|
189
|
+
set PDFDANCER_API_TOKEN=42
|
|
151
190
|
venv\Scripts\python -m pytest tests/ -v --maxfail=3
|
|
152
191
|
|
|
153
192
|
- name: Build & Validate (Windows)
|
|
@@ -155,4 +194,4 @@ jobs:
|
|
|
155
194
|
shell: cmd
|
|
156
195
|
run: |
|
|
157
196
|
venv\Scripts\python -m build
|
|
158
|
-
venv\Scripts\python -m twine check dist/*
|
|
197
|
+
venv\Scripts\python -m twine check dist/*
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: SDK Backward Compatibility Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
repository_dispatch:
|
|
5
|
+
types: [api-preview-deployed]
|
|
6
|
+
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
api_url:
|
|
10
|
+
description: 'API base URL to test against'
|
|
11
|
+
required: true
|
|
12
|
+
type: string
|
|
13
|
+
sdk_token:
|
|
14
|
+
description: 'API token for testing'
|
|
15
|
+
required: true
|
|
16
|
+
type: string
|
|
17
|
+
default: '42'
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
get-tags:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
outputs:
|
|
23
|
+
tags: ${{ steps.tags.outputs.tags }}
|
|
24
|
+
steps:
|
|
25
|
+
- name: Fetch tags
|
|
26
|
+
id: tags
|
|
27
|
+
run: |
|
|
28
|
+
TAGS=$(git ls-remote --tags https://github.com/MenschMachine/pdfdancer-client-python.git \
|
|
29
|
+
| grep -v '\^{}' \
|
|
30
|
+
| awk -F'/' '{print $NF}' \
|
|
31
|
+
| grep '^v' \
|
|
32
|
+
| sort -V \
|
|
33
|
+
| tail -3 \
|
|
34
|
+
| jq -R -s -c 'split("\n") | map(select(length > 0))')
|
|
35
|
+
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"
|
|
36
|
+
echo "Last 3 SDK tags: $TAGS"
|
|
37
|
+
|
|
38
|
+
test-compat:
|
|
39
|
+
needs: get-tags
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
timeout-minutes: 60
|
|
42
|
+
strategy:
|
|
43
|
+
fail-fast: false
|
|
44
|
+
max-parallel: 1
|
|
45
|
+
matrix:
|
|
46
|
+
tag: ${{ fromJSON(needs.get-tags.outputs.tags) }}
|
|
47
|
+
|
|
48
|
+
steps:
|
|
49
|
+
- name: Checkout SDK at ${{ matrix.tag }}
|
|
50
|
+
uses: actions/checkout@v4
|
|
51
|
+
with:
|
|
52
|
+
repository: MenschMachine/pdfdancer-client-python
|
|
53
|
+
ref: ${{ matrix.tag }}
|
|
54
|
+
path: sdk
|
|
55
|
+
|
|
56
|
+
- name: Set up Python
|
|
57
|
+
uses: actions/setup-python@v5
|
|
58
|
+
with:
|
|
59
|
+
python-version: '3.12'
|
|
60
|
+
|
|
61
|
+
- name: Create virtual environment
|
|
62
|
+
run: python -m venv venv
|
|
63
|
+
|
|
64
|
+
- name: Install SDK at ${{ matrix.tag }}
|
|
65
|
+
run: |
|
|
66
|
+
venv/bin/pip install --upgrade pip
|
|
67
|
+
venv/bin/pip install -e "./sdk[dev]"
|
|
68
|
+
|
|
69
|
+
- name: Run e2e tests
|
|
70
|
+
working-directory: sdk
|
|
71
|
+
run: |
|
|
72
|
+
PDFDANCER_BASE_URL=${{ github.event.client_payload.api_url || inputs.api_url }} \
|
|
73
|
+
PDFDANCER_API_TOKEN=${{ github.event.client_payload.sdk_token || inputs.sdk_token }} \
|
|
74
|
+
../venv/bin/python -m pytest -v -x \
|
|
75
|
+
-k "not test_redact_multiple_paths and not test_context_manager_vs_manual_apply" \
|
|
76
|
+
--junitxml=test-results.xml
|
|
77
|
+
env:
|
|
78
|
+
PYTHONWARNINGS: "ignore:Unverified HTTPS request"
|
|
79
|
+
|
|
80
|
+
- name: Upload test results
|
|
81
|
+
if: failure()
|
|
82
|
+
uses: actions/upload-artifact@v4
|
|
83
|
+
with:
|
|
84
|
+
name: test-results-${{ matrix.tag }}
|
|
85
|
+
path: |
|
|
86
|
+
sdk/test-results.xml
|
|
87
|
+
sdk/.pytest_cache/
|
|
88
|
+
retention-days: 7
|
|
@@ -28,6 +28,7 @@ from .models import (
|
|
|
28
28
|
ImageTransformRequest,
|
|
29
29
|
ImageTransformType,
|
|
30
30
|
Line,
|
|
31
|
+
ModifyPathRequest,
|
|
31
32
|
ObjectRef,
|
|
32
33
|
ObjectType,
|
|
33
34
|
Orientation,
|
|
@@ -36,6 +37,7 @@ from .models import (
|
|
|
36
37
|
Paragraph,
|
|
37
38
|
Path,
|
|
38
39
|
PathGroupInfo,
|
|
40
|
+
PathObjectRef,
|
|
39
41
|
PathSegment,
|
|
40
42
|
Point,
|
|
41
43
|
Position,
|
|
@@ -55,7 +57,7 @@ from .paragraph_builder import ParagraphBuilder
|
|
|
55
57
|
from .path_builder import BezierBuilder, LineBuilder, PathBuilder
|
|
56
58
|
from .text_line_builder import TextLineBuilder
|
|
57
59
|
|
|
58
|
-
__version__ = "0.3.
|
|
60
|
+
__version__ = "0.3.13"
|
|
59
61
|
__all__ = [
|
|
60
62
|
"PDFDancer",
|
|
61
63
|
"ParagraphBuilder",
|
|
@@ -65,6 +67,8 @@ __all__ = [
|
|
|
65
67
|
"LineBuilder",
|
|
66
68
|
"BezierBuilder",
|
|
67
69
|
"ObjectRef",
|
|
70
|
+
"PathObjectRef",
|
|
71
|
+
"ModifyPathRequest",
|
|
68
72
|
"Position",
|
|
69
73
|
"ObjectType",
|
|
70
74
|
"Font",
|
|
@@ -1895,3 +1895,85 @@ class ImageTransformRequest:
|
|
|
1895
1895
|
result["fillColor"] = self.fill_color
|
|
1896
1896
|
|
|
1897
1897
|
return result
|
|
1898
|
+
|
|
1899
|
+
|
|
1900
|
+
@dataclass
|
|
1901
|
+
class ModifyPathRequest:
|
|
1902
|
+
"""Request to modify path stroke and fill colors.
|
|
1903
|
+
|
|
1904
|
+
Parameters:
|
|
1905
|
+
- object_ref: Reference to the path to modify.
|
|
1906
|
+
- stroke_color: New stroke color (optional - null means don't change).
|
|
1907
|
+
- fill_color: New fill color (optional - null means don't change).
|
|
1908
|
+
|
|
1909
|
+
Example:
|
|
1910
|
+
```python
|
|
1911
|
+
req = ModifyPathRequest(object_ref=path_ref, stroke_color=Color(255, 0, 0), fill_color=None)
|
|
1912
|
+
payload = req.to_dict()
|
|
1913
|
+
```
|
|
1914
|
+
"""
|
|
1915
|
+
|
|
1916
|
+
object_ref: ObjectRef
|
|
1917
|
+
stroke_color: Optional[Color] = None
|
|
1918
|
+
fill_color: Optional[Color] = None
|
|
1919
|
+
|
|
1920
|
+
def to_dict(self) -> dict:
|
|
1921
|
+
"""Convert to dictionary for JSON serialization."""
|
|
1922
|
+
result: Dict[str, Any] = {
|
|
1923
|
+
"ref": self.object_ref.to_dict(),
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
if self.stroke_color is not None:
|
|
1927
|
+
result["strokeColor"] = {
|
|
1928
|
+
"red": self.stroke_color.r,
|
|
1929
|
+
"green": self.stroke_color.g,
|
|
1930
|
+
"blue": self.stroke_color.b,
|
|
1931
|
+
"alpha": self.stroke_color.a,
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
if self.fill_color is not None:
|
|
1935
|
+
result["fillColor"] = {
|
|
1936
|
+
"red": self.fill_color.r,
|
|
1937
|
+
"green": self.fill_color.g,
|
|
1938
|
+
"blue": self.fill_color.b,
|
|
1939
|
+
"alpha": self.fill_color.a,
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
return result
|
|
1943
|
+
|
|
1944
|
+
|
|
1945
|
+
class PathObjectRef(ObjectRef):
|
|
1946
|
+
"""
|
|
1947
|
+
Reference to a path object with stroke and fill color information.
|
|
1948
|
+
|
|
1949
|
+
Parameters (typically provided by the server):
|
|
1950
|
+
- internal_id: Identifier of the path object.
|
|
1951
|
+
- position: Position of the path.
|
|
1952
|
+
- object_type: Should be ObjectType.PATH.
|
|
1953
|
+
- stroke_color: Stroke/outline color of the path (optional).
|
|
1954
|
+
- fill_color: Fill color of the path (optional).
|
|
1955
|
+
|
|
1956
|
+
Usage:
|
|
1957
|
+
- Returned by find/snapshot APIs when querying paths.
|
|
1958
|
+
- Pass to ModifyPathRequest to update path colors.
|
|
1959
|
+
"""
|
|
1960
|
+
|
|
1961
|
+
def __init__(
|
|
1962
|
+
self,
|
|
1963
|
+
internal_id: str,
|
|
1964
|
+
position: Position,
|
|
1965
|
+
object_type: ObjectType,
|
|
1966
|
+
stroke_color: Optional[Color] = None,
|
|
1967
|
+
fill_color: Optional[Color] = None,
|
|
1968
|
+
):
|
|
1969
|
+
super().__init__(internal_id, position, object_type)
|
|
1970
|
+
self.stroke_color = stroke_color
|
|
1971
|
+
self.fill_color = fill_color
|
|
1972
|
+
|
|
1973
|
+
def get_stroke_color(self) -> Optional[Color]:
|
|
1974
|
+
"""Get the stroke/outline color of the path."""
|
|
1975
|
+
return self.stroke_color
|
|
1976
|
+
|
|
1977
|
+
def get_fill_color(self) -> Optional[Color]:
|
|
1978
|
+
"""Get the fill color of the path."""
|
|
1979
|
+
return self.fill_color
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/pdfdancer_v1.py
RENAMED
|
@@ -47,6 +47,7 @@ from .models import (
|
|
|
47
47
|
FontType,
|
|
48
48
|
FormFieldRef,
|
|
49
49
|
Image,
|
|
50
|
+
ModifyPathRequest,
|
|
50
51
|
ModifyRequest,
|
|
51
52
|
ModifyTextRequest,
|
|
52
53
|
MoveRequest,
|
|
@@ -58,6 +59,7 @@ from .models import (
|
|
|
58
59
|
PageSize,
|
|
59
60
|
PageSnapshot,
|
|
60
61
|
Paragraph,
|
|
62
|
+
PathObjectRef,
|
|
61
63
|
Position,
|
|
62
64
|
PositionMode,
|
|
63
65
|
RedactRequest,
|
|
@@ -2755,6 +2757,38 @@ class PDFDancer:
|
|
|
2755
2757
|
self._invalidate_snapshots()
|
|
2756
2758
|
return result
|
|
2757
2759
|
|
|
2760
|
+
def _modify_path(
|
|
2761
|
+
self,
|
|
2762
|
+
object_ref: ObjectRef,
|
|
2763
|
+
stroke_color: Optional[Color],
|
|
2764
|
+
fill_color: Optional[Color],
|
|
2765
|
+
) -> CommandResult:
|
|
2766
|
+
"""
|
|
2767
|
+
Modifies the stroke and fill colors of a path object.
|
|
2768
|
+
|
|
2769
|
+
Args:
|
|
2770
|
+
object_ref: Reference to the path to modify.
|
|
2771
|
+
stroke_color: New stroke color (None means don't change).
|
|
2772
|
+
fill_color: New fill color (None means don't change).
|
|
2773
|
+
|
|
2774
|
+
Returns:
|
|
2775
|
+
CommandResult indicating success or failure.
|
|
2776
|
+
"""
|
|
2777
|
+
if object_ref is None:
|
|
2778
|
+
raise ValidationException("Object reference cannot be null")
|
|
2779
|
+
|
|
2780
|
+
request_data = ModifyPathRequest(
|
|
2781
|
+
object_ref=object_ref,
|
|
2782
|
+
stroke_color=stroke_color,
|
|
2783
|
+
fill_color=fill_color,
|
|
2784
|
+
).to_dict()
|
|
2785
|
+
response = self._make_request("PUT", "/pdf/modify/path", data=request_data)
|
|
2786
|
+
result = CommandResult.from_dict(response.json())
|
|
2787
|
+
|
|
2788
|
+
# Invalidate snapshot caches after mutation
|
|
2789
|
+
self._invalidate_snapshots()
|
|
2790
|
+
return result
|
|
2791
|
+
|
|
2758
2792
|
# Font Operations
|
|
2759
2793
|
|
|
2760
2794
|
def find_fonts(self, font_name: str, font_size: int) -> List[Font]:
|
|
@@ -3159,6 +3193,43 @@ class PDFDancer:
|
|
|
3159
3193
|
value=obj_data["value"] if "value" in obj_data else None,
|
|
3160
3194
|
)
|
|
3161
3195
|
|
|
3196
|
+
def _parse_path_object_ref(self, obj_data: dict) -> PathObjectRef:
|
|
3197
|
+
"""Parse JSON object data into PathObjectRef instance with color information."""
|
|
3198
|
+
position_data = obj_data.get("position", {})
|
|
3199
|
+
position = self._parse_position(position_data) if position_data else None
|
|
3200
|
+
|
|
3201
|
+
object_type = ObjectType(obj_data["type"])
|
|
3202
|
+
|
|
3203
|
+
# Parse stroke color if present
|
|
3204
|
+
stroke_color = None
|
|
3205
|
+
stroke_color_data = obj_data.get("strokeColor")
|
|
3206
|
+
if isinstance(stroke_color_data, dict):
|
|
3207
|
+
red = stroke_color_data.get("red")
|
|
3208
|
+
green = stroke_color_data.get("green")
|
|
3209
|
+
blue = stroke_color_data.get("blue")
|
|
3210
|
+
alpha = stroke_color_data.get("alpha", 255)
|
|
3211
|
+
if all(isinstance(v, int) for v in [red, green, blue]):
|
|
3212
|
+
stroke_color = Color(red, green, blue, alpha)
|
|
3213
|
+
|
|
3214
|
+
# Parse fill color if present
|
|
3215
|
+
fill_color = None
|
|
3216
|
+
fill_color_data = obj_data.get("fillColor")
|
|
3217
|
+
if isinstance(fill_color_data, dict):
|
|
3218
|
+
red = fill_color_data.get("red")
|
|
3219
|
+
green = fill_color_data.get("green")
|
|
3220
|
+
blue = fill_color_data.get("blue")
|
|
3221
|
+
alpha = fill_color_data.get("alpha", 255)
|
|
3222
|
+
if all(isinstance(v, int) for v in [red, green, blue]):
|
|
3223
|
+
fill_color = Color(red, green, blue, alpha)
|
|
3224
|
+
|
|
3225
|
+
return PathObjectRef(
|
|
3226
|
+
internal_id=obj_data["internalId"] if "internalId" in obj_data else None,
|
|
3227
|
+
position=position,
|
|
3228
|
+
object_type=object_type,
|
|
3229
|
+
stroke_color=stroke_color,
|
|
3230
|
+
fill_color=fill_color,
|
|
3231
|
+
)
|
|
3232
|
+
|
|
3162
3233
|
@staticmethod
|
|
3163
3234
|
def _parse_position(pos_data: dict) -> Position:
|
|
3164
3235
|
"""Parse JSON position data into Position instance."""
|
|
@@ -3473,6 +3544,9 @@ class PDFDancer:
|
|
|
3473
3544
|
):
|
|
3474
3545
|
# Parse as FormFieldRef to capture name and value
|
|
3475
3546
|
elements.append(self._parse_form_field_ref(elem_data))
|
|
3547
|
+
elif elem_type == ObjectType.PATH:
|
|
3548
|
+
# Parse as PathObjectRef to capture stroke/fill colors
|
|
3549
|
+
elements.append(self._parse_path_object_ref(elem_data))
|
|
3476
3550
|
else:
|
|
3477
3551
|
# Parse as basic ObjectRef
|
|
3478
3552
|
elements.append(self._parse_object_ref(elem_data))
|
|
@@ -3526,9 +3600,7 @@ class PDFDancer:
|
|
|
3526
3600
|
self._client.close()
|
|
3527
3601
|
|
|
3528
3602
|
def _to_path_objects(self, refs: List[ObjectRef]) -> List[PathObject]:
|
|
3529
|
-
return [
|
|
3530
|
-
PathObject(self, ref.internal_id, ref.type, ref.position) for ref in refs
|
|
3531
|
-
]
|
|
3603
|
+
return [PathObject(self, ref) for ref in refs]
|
|
3532
3604
|
|
|
3533
3605
|
def _to_paragraph_objects(self, refs: List[TextObjectRef]) -> List[ParagraphObject]:
|
|
3534
3606
|
return [ParagraphObject(self, ref) for ref in refs]
|
|
@@ -4,7 +4,7 @@ import sys
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from typing import TYPE_CHECKING, Optional
|
|
6
6
|
|
|
7
|
-
from . import FormFieldRef, ObjectRef, ObjectType, Point, Position, TextObjectRef
|
|
7
|
+
from . import FormFieldRef, ObjectRef, ObjectType, PathObjectRef, Point, Position, TextObjectRef
|
|
8
8
|
from .exceptions import ValidationException
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
@@ -86,11 +86,44 @@ class PDFObjectBase:
|
|
|
86
86
|
class PathObject(PDFObjectBase):
|
|
87
87
|
"""Represents a vector path object inside a PDF page."""
|
|
88
88
|
|
|
89
|
+
def __init__(self, client: "PDFDancer", object_ref):
|
|
90
|
+
"""
|
|
91
|
+
Initialize a PathObject.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
client: PDFDancer client instance
|
|
95
|
+
object_ref: ObjectRef or PathObjectRef with path data
|
|
96
|
+
"""
|
|
97
|
+
super().__init__(
|
|
98
|
+
client, object_ref.internal_id, object_ref.type, object_ref.position
|
|
99
|
+
)
|
|
100
|
+
self._object_ref = object_ref
|
|
101
|
+
|
|
89
102
|
@property
|
|
90
103
|
def bounding_box(self) -> Optional[BoundingRect]:
|
|
91
104
|
"""Optional bounding rectangle (if available)."""
|
|
92
105
|
return self.position.bounding_rect
|
|
93
106
|
|
|
107
|
+
def edit(self) -> PathEditSession:
|
|
108
|
+
"""Start a fluent editing session to modify path colors."""
|
|
109
|
+
return PathEditSession(self._client, self.object_ref())
|
|
110
|
+
|
|
111
|
+
def object_ref(self):
|
|
112
|
+
"""Return an ObjectRef for this path."""
|
|
113
|
+
return self._object_ref
|
|
114
|
+
|
|
115
|
+
def get_stroke_color(self) -> Optional["Color"]:
|
|
116
|
+
"""Get the stroke/outline color of the path, or None if not set."""
|
|
117
|
+
if isinstance(self._object_ref, PathObjectRef):
|
|
118
|
+
return self._object_ref.get_stroke_color()
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
def get_fill_color(self) -> Optional["Color"]:
|
|
122
|
+
"""Get the fill color of the path, or None if not set."""
|
|
123
|
+
if isinstance(self._object_ref, PathObjectRef):
|
|
124
|
+
return self._object_ref.get_fill_color()
|
|
125
|
+
return None
|
|
126
|
+
|
|
94
127
|
def __eq__(self, other):
|
|
95
128
|
if not isinstance(other, PathObject):
|
|
96
129
|
return False
|
|
@@ -748,3 +781,61 @@ class FormFieldObject(PDFObjectBase):
|
|
|
748
781
|
and self.name == other.name
|
|
749
782
|
and self.value == other.value
|
|
750
783
|
)
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
class PathEditSession:
|
|
787
|
+
"""
|
|
788
|
+
Fluent editing helper for modifying path stroke and fill colors.
|
|
789
|
+
"""
|
|
790
|
+
|
|
791
|
+
def __init__(self, client: "PDFDancer", object_ref):
|
|
792
|
+
self._client = client
|
|
793
|
+
self._object_ref = object_ref
|
|
794
|
+
self._stroke_color = None
|
|
795
|
+
self._fill_color = None
|
|
796
|
+
|
|
797
|
+
def __enter__(self):
|
|
798
|
+
return self
|
|
799
|
+
|
|
800
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
801
|
+
if exc_type:
|
|
802
|
+
return False
|
|
803
|
+
self.apply()
|
|
804
|
+
return False
|
|
805
|
+
|
|
806
|
+
def stroke_color(self, color) -> "PathEditSession":
|
|
807
|
+
"""
|
|
808
|
+
Set the stroke/outline color.
|
|
809
|
+
|
|
810
|
+
Args:
|
|
811
|
+
color: The stroke color (Color object)
|
|
812
|
+
|
|
813
|
+
Returns:
|
|
814
|
+
Self for method chaining
|
|
815
|
+
"""
|
|
816
|
+
self._stroke_color = color
|
|
817
|
+
return self
|
|
818
|
+
|
|
819
|
+
def fill_color(self, color) -> "PathEditSession":
|
|
820
|
+
"""
|
|
821
|
+
Set the fill color.
|
|
822
|
+
|
|
823
|
+
Args:
|
|
824
|
+
color: The fill color (Color object)
|
|
825
|
+
|
|
826
|
+
Returns:
|
|
827
|
+
Self for method chaining
|
|
828
|
+
"""
|
|
829
|
+
self._fill_color = color
|
|
830
|
+
return self
|
|
831
|
+
|
|
832
|
+
def apply(self):
|
|
833
|
+
"""
|
|
834
|
+
Apply the color modifications to the path.
|
|
835
|
+
|
|
836
|
+
Returns:
|
|
837
|
+
CommandResult indicating success or failure
|
|
838
|
+
"""
|
|
839
|
+
return self._client._modify_path(
|
|
840
|
+
self._object_ref, self._stroke_color, self._fill_color
|
|
841
|
+
)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
.api-url
|
|
2
1
|
.flake8
|
|
3
2
|
.gitignore
|
|
4
3
|
.gitmodules
|
|
@@ -16,6 +15,7 @@ test.sh
|
|
|
16
15
|
.github/workflows/ci.yml
|
|
17
16
|
.github/workflows/daily-tests.yml
|
|
18
17
|
.github/workflows/release.yml
|
|
18
|
+
.github/workflows/sdk-backward-compat.yml
|
|
19
19
|
docs/capabilities/.gitkeep
|
|
20
20
|
docs/capabilities/CLEAR_CLIPPING.md
|
|
21
21
|
media/logo-orange-512h.webp
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_context_manager.py
RENAMED
|
@@ -398,6 +398,12 @@ def test_context_manager_example_from_docs():
|
|
|
398
398
|
)
|
|
399
399
|
|
|
400
400
|
|
|
401
|
+
@pytest.mark.skip(reason="""
|
|
402
|
+
The following test is disabled because it fails intermittently:
|
|
403
|
+
Minor variations in PDF byte output result in failures, even though output is visually and functionally identical.
|
|
404
|
+
Such spurious differences can be caused by library version changes, metadata, timestamps, and non-semantic structure within PDFs.
|
|
405
|
+
Therefore, strict byte count comparison is too brittle for a reliable automated test.
|
|
406
|
+
""")
|
|
401
407
|
def test_context_manager_vs_manual_apply():
|
|
402
408
|
"""Test that context manager produces same result as manual apply()"""
|
|
403
409
|
base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from pdfdancer import ObjectType
|
|
3
|
+
from pdfdancer import Color, ObjectType
|
|
4
4
|
from pdfdancer.pdfdancer_v1 import PDFDancer
|
|
5
5
|
from tests.e2e import _require_env_and_fixture
|
|
6
6
|
from tests.e2e.pdf_assertions import PDFAssertions
|
|
@@ -79,3 +79,50 @@ def test_move_path():
|
|
|
79
79
|
.assert_no_path_at(80, 720)
|
|
80
80
|
.assert_path_is_at("PATH_0_000001", 50.1, 100)
|
|
81
81
|
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_modify_path_colors():
|
|
85
|
+
"""Test modifying stroke and fill colors of a path."""
|
|
86
|
+
base_url, token, pdf_path = _require_env_and_fixture("basic-paths.pdf")
|
|
87
|
+
|
|
88
|
+
with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
|
|
89
|
+
# PATH_0_000001 is a line at (80, 720)
|
|
90
|
+
path = pdf.page(1).select_paths_at(80, 720)[0]
|
|
91
|
+
assert path.internal_id == "PATH_0_000001"
|
|
92
|
+
|
|
93
|
+
# Modify the stroke color to red
|
|
94
|
+
red = Color(255, 0, 0)
|
|
95
|
+
result = path.edit().stroke_color(red).apply()
|
|
96
|
+
assert result.success, f"Expected success but got: {result.message}"
|
|
97
|
+
|
|
98
|
+
# Re-fetch path via select_paths() to verify the stroke color was actually changed
|
|
99
|
+
# Note: select_paths_at() goes to API which doesn't return colors,
|
|
100
|
+
# select_paths() uses snapshot which includes color data
|
|
101
|
+
paths_after_stroke = [p for p in pdf.select_paths() if p.internal_id == "PATH_0_000001"]
|
|
102
|
+
assert len(paths_after_stroke) == 1
|
|
103
|
+
path_after_stroke = paths_after_stroke[0]
|
|
104
|
+
stroke_color = path_after_stroke.get_stroke_color()
|
|
105
|
+
assert stroke_color is not None, "Expected stroke color to be set after modification"
|
|
106
|
+
assert stroke_color.r == 255, f"Expected red=255 but got {stroke_color.r}"
|
|
107
|
+
assert stroke_color.g == 0, f"Expected green=0 but got {stroke_color.g}"
|
|
108
|
+
assert stroke_color.b == 0, f"Expected blue=0 but got {stroke_color.b}"
|
|
109
|
+
|
|
110
|
+
# Modify both stroke and fill colors
|
|
111
|
+
blue = Color(0, 0, 255)
|
|
112
|
+
result = path.edit().stroke_color(red).fill_color(blue).apply()
|
|
113
|
+
assert result.success, f"Expected success but got: {result.message}"
|
|
114
|
+
|
|
115
|
+
# Re-fetch via select_paths() to verify both colors were actually changed
|
|
116
|
+
paths_after_both = [p for p in pdf.select_paths() if p.internal_id == "PATH_0_000001"]
|
|
117
|
+
assert len(paths_after_both) == 1
|
|
118
|
+
path_after_both = paths_after_both[0]
|
|
119
|
+
stroke_color = path_after_both.get_stroke_color()
|
|
120
|
+
fill_color = path_after_both.get_fill_color()
|
|
121
|
+
assert stroke_color is not None, "Expected stroke color to be set"
|
|
122
|
+
assert fill_color is not None, "Expected fill color to be set after modification"
|
|
123
|
+
assert stroke_color.r == 255, f"Expected stroke red=255 but got {stroke_color.r}"
|
|
124
|
+
assert stroke_color.g == 0, f"Expected stroke green=0 but got {stroke_color.g}"
|
|
125
|
+
assert stroke_color.b == 0, f"Expected stroke blue=0 but got {stroke_color.b}"
|
|
126
|
+
assert fill_color.r == 0, f"Expected fill red=0 but got {fill_color.r}"
|
|
127
|
+
assert fill_color.g == 0, f"Expected fill green=0 but got {fill_color.g}"
|
|
128
|
+
assert fill_color.b == 255, f"Expected fill blue=255 but got {fill_color.b}"
|
|
@@ -202,6 +202,7 @@ def test_redact_path():
|
|
|
202
202
|
assertions.assert_number_of_paths(8)
|
|
203
203
|
|
|
204
204
|
|
|
205
|
+
@pytest.mark.skip(reason="TODO cannot make a meaningful assertion currently")
|
|
205
206
|
def test_redact_multiple_paths():
|
|
206
207
|
"""Test batch redacting multiple paths"""
|
|
207
208
|
base_url, token, pdf_path = _require_env_and_fixture("basic-paths.pdf")
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_pdf_object_equality.py
RENAMED
|
@@ -6,7 +6,7 @@ from unittest.mock import Mock
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
-
from pdfdancer import Color, ObjectType, Position, TextObjectRef
|
|
9
|
+
from pdfdancer import Color, ObjectRef, ObjectType, Position, TextObjectRef
|
|
10
10
|
from pdfdancer.types import (
|
|
11
11
|
FormFieldObject,
|
|
12
12
|
FormObject,
|
|
@@ -24,9 +24,10 @@ class TestPDFObjectEquality:
|
|
|
24
24
|
"""PathObject instances with same internal_id and type should be equal."""
|
|
25
25
|
mock_client = Mock()
|
|
26
26
|
position = Position.at_page(1)
|
|
27
|
+
ref = ObjectRef("id123", position, ObjectType.PATH)
|
|
27
28
|
|
|
28
|
-
obj1 = PathObject(mock_client,
|
|
29
|
-
obj2 = PathObject(mock_client,
|
|
29
|
+
obj1 = PathObject(mock_client, ref)
|
|
30
|
+
obj2 = PathObject(mock_client, ref)
|
|
30
31
|
|
|
31
32
|
assert obj1 == obj2
|
|
32
33
|
|
|
@@ -34,9 +35,11 @@ class TestPDFObjectEquality:
|
|
|
34
35
|
"""PathObject instances with different internal_id should not be equal."""
|
|
35
36
|
mock_client = Mock()
|
|
36
37
|
position = Position.at_page(1)
|
|
38
|
+
ref1 = ObjectRef("id123", position, ObjectType.PATH)
|
|
39
|
+
ref2 = ObjectRef("id456", position, ObjectType.PATH)
|
|
37
40
|
|
|
38
|
-
obj1 = PathObject(mock_client,
|
|
39
|
-
obj2 = PathObject(mock_client,
|
|
41
|
+
obj1 = PathObject(mock_client, ref1)
|
|
42
|
+
obj2 = PathObject(mock_client, ref2)
|
|
40
43
|
|
|
41
44
|
assert obj1 != obj2
|
|
42
45
|
|
|
@@ -46,8 +49,11 @@ class TestPDFObjectEquality:
|
|
|
46
49
|
position1 = Position.at_page(1)
|
|
47
50
|
position2 = Position.at_page(2)
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
ref1 = ObjectRef("id123", position1, ObjectType.PATH)
|
|
53
|
+
ref2 = ObjectRef("id123", position2, ObjectType.PATH)
|
|
54
|
+
|
|
55
|
+
obj1 = PathObject(mock_client, ref1)
|
|
56
|
+
obj2 = PathObject(mock_client, ref2)
|
|
51
57
|
|
|
52
58
|
assert obj1 != obj2
|
|
53
59
|
|
|
@@ -55,8 +61,9 @@ class TestPDFObjectEquality:
|
|
|
55
61
|
"""PathObject should not equal non-PathObject."""
|
|
56
62
|
mock_client = Mock()
|
|
57
63
|
position = Position.at_page(1)
|
|
64
|
+
path_ref = ObjectRef("id123", position, ObjectType.PATH)
|
|
58
65
|
|
|
59
|
-
obj1 = PathObject(mock_client,
|
|
66
|
+
obj1 = PathObject(mock_client, path_ref)
|
|
60
67
|
obj2 = ImageObject(mock_client, "id123", ObjectType.IMAGE, position)
|
|
61
68
|
|
|
62
69
|
assert obj1 != obj2
|
|
@@ -296,8 +303,9 @@ class TestPDFObjectEquality:
|
|
|
296
303
|
"""PDFObjectBase subclasses should not equal None."""
|
|
297
304
|
mock_client = Mock()
|
|
298
305
|
position = Position.at_page(1)
|
|
306
|
+
ref = ObjectRef("id123", position, ObjectType.PATH)
|
|
299
307
|
|
|
300
|
-
obj = PathObject(mock_client,
|
|
308
|
+
obj = PathObject(mock_client, ref)
|
|
301
309
|
|
|
302
310
|
assert obj != None
|
|
303
311
|
|
|
@@ -305,7 +313,8 @@ class TestPDFObjectEquality:
|
|
|
305
313
|
"""PDFObjectBase subclasses should not equal strings."""
|
|
306
314
|
mock_client = Mock()
|
|
307
315
|
position = Position.at_page(1)
|
|
316
|
+
ref = ObjectRef("id123", position, ObjectType.PATH)
|
|
308
317
|
|
|
309
|
-
obj = PathObject(mock_client,
|
|
318
|
+
obj = PathObject(mock_client, ref)
|
|
310
319
|
|
|
311
320
|
assert obj != "id123"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
http://46.225.120.69:8080
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/.claude/commands/discuss.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/.github/workflows/daily-tests.yml
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/.github/workflows/release.yml
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
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/docs/capabilities/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/media/logo-orange-512h.webp
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/media/logo-orange-60h.webp
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/media/logo-silver-512h.webp
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/media/logo-silver-60h.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/exceptions.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/fingerprint.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/image_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/page_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/paragraph_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/path_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/src/pdfdancer/text_line_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.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/pdf_assertions.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_acroform.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_bezier_builder.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_clipping.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_form_x_objects.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_image_transform.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_line_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_paragraph.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_path_group.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_pdfdancer.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_positioning.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_snapshot.py
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_template_replace.py
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/e2e/test_text_line_edit.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/Roboto-Regular.ttf
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/Showcase.pdf
RENAMED
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/basic-paths.pdf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/fixtures/logo-80.png
RENAMED
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_anonymous_token.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_openapi_compliance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.3.12 → pdfdancer_client_python-0.3.13}/tests/test_standard_fonts.py
RENAMED
|
File without changes
|