pdfdancer-client-python 0.3.10__tar.gz → 0.3.12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. pdfdancer_client_python-0.3.12/.api-url +1 -0
  2. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/.github/workflows/ci.yml +31 -4
  3. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/.github/workflows/daily-tests.yml +2 -0
  4. pdfdancer_client_python-0.3.12/.github/workflows/release.yml +84 -0
  5. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/.gitignore +2 -0
  6. pdfdancer_client_python-0.3.12/.gitmodules +3 -0
  7. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/PKG-INFO +11 -10
  8. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/README.md +9 -8
  9. pdfdancer_client_python-0.3.12/docs/capabilities/.gitkeep +0 -0
  10. pdfdancer_client_python-0.3.12/docs/capabilities/CLEAR_CLIPPING.md +61 -0
  11. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/pyproject.toml +2 -2
  12. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/release.py +51 -5
  13. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer/__init__.py +7 -1
  14. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer/exceptions.py +9 -0
  15. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer/models.py +19 -0
  16. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer/pdfdancer_v1.py +196 -4
  17. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer/types.py +61 -0
  18. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer_client_python.egg-info/PKG-INFO +11 -10
  19. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer_client_python.egg-info/SOURCES.txt +11 -3
  20. pdfdancer_client_python-0.3.12/tests/e2e/pdf_assertions.py +1783 -0
  21. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_acroform.py +2 -2
  22. pdfdancer_client_python-0.3.12/tests/e2e/test_clipping.py +323 -0
  23. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_form_x_objects.py +1 -1
  24. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_image.py +3 -3
  25. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_paragraph.py +24 -12
  26. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_path.py +5 -5
  27. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_path_comprehensive.py +1 -1
  28. pdfdancer_client_python-0.3.12/tests/e2e/test_path_group.py +244 -0
  29. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_redact.py +2 -2
  30. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_template_replace.py +1 -1
  31. pdfdancer_client_python-0.3.12/tests/e2e/test_template_replace_linebreak.py +141 -0
  32. pdfdancer_client_python-0.3.12/tests/fixtures/Asimovian-Regular.ttf +0 -0
  33. pdfdancer_client_python-0.3.12/tests/fixtures/Roboto-Regular.ttf +0 -0
  34. pdfdancer_client_python-0.3.12/tests/fixtures/invisible-content-clipping-test.pdf +0 -0
  35. pdfdancer_client_python-0.3.10/docs/api-schemas/v0.yml +0 -5300
  36. pdfdancer_client_python-0.3.10/docs/api-schemas/v1.yml +0 -5387
  37. pdfdancer_client_python-0.3.10/tests/e2e/pdf_assertions.py +0 -817
  38. pdfdancer_client_python-0.3.10/update-api-spec.sh +0 -5
  39. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/.claude/commands/discuss.md +0 -0
  40. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/.claude/commands/implement-new-api-features.md +0 -0
  41. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/.flake8 +0 -0
  42. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/CLAUDE.md +0 -0
  43. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/LICENSE +0 -0
  44. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/NOTICE +0 -0
  45. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/TODO.md +0 -0
  46. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/check.py +0 -0
  47. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/media/logo-orange-512h.webp +0 -0
  48. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/media/logo-orange-60h.webp +0 -0
  49. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/media/logo-silver-512h.webp +0 -0
  50. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/media/logo-silver-60h.webp +0 -0
  51. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/setup.cfg +0 -0
  52. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer/fingerprint.py +0 -0
  53. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer/image_builder.py +0 -0
  54. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer/page_builder.py +0 -0
  55. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer/paragraph_builder.py +0 -0
  56. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer/path_builder.py +0 -0
  57. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer/text_line_builder.py +0 -0
  58. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
  59. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
  60. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
  61. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/test.sh +0 -0
  62. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/__init__.py +0 -0
  63. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/conftest.py +0 -0
  64. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/__init__.py +0 -0
  65. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_bezier_builder.py +0 -0
  66. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_context_manager.py +0 -0
  67. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_image_transform.py +0 -0
  68. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_line.py +0 -0
  69. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_line_builder.py +0 -0
  70. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_new_pdf.py +0 -0
  71. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_page.py +0 -0
  72. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_path_builder.py +0 -0
  73. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_path_builder_rectangle.py +0 -0
  74. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_pdfdancer.py +0 -0
  75. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_positioning.py +0 -0
  76. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_rectangle_builder.py +0 -0
  77. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_singular_selection.py +0 -0
  78. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_snapshot.py +0 -0
  79. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/e2e/test_text_line_edit.py +0 -0
  80. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/fixtures/DancingScript-Regular.ttf +0 -0
  81. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/fixtures/Empty.pdf +0 -0
  82. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
  83. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/fixtures/Showcase.pdf +0 -0
  84. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/fixtures/basic-paths.pdf +0 -0
  85. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/fixtures/form-xobject-example.pdf +0 -0
  86. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/fixtures/logo-80.png +0 -0
  87. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/fixtures/mixed-form-types.pdf +0 -0
  88. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/test_anonymous_token.py +0 -0
  89. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/test_fingerprint.py +0 -0
  90. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/test_models.py +0 -0
  91. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/test_openapi_compliance.py +0 -0
  92. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/test_path_models.py +0 -0
  93. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/test_pdf_object_equality.py +0 -0
  94. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/test_rate_limit.py +0 -0
  95. {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.12}/tests/test_standard_fonts.py +0 -0
@@ -0,0 +1 @@
1
+ http://46.225.120.69:8080
@@ -2,7 +2,7 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ '**' ]
5
+ branches: [ 'main' ]
6
6
  pull_request:
7
7
  branches: [ '**' ]
8
8
  workflow_dispatch:
@@ -20,6 +20,19 @@ jobs:
20
20
 
21
21
  steps:
22
22
  - uses: actions/checkout@v4
23
+ with:
24
+ submodules: true
25
+
26
+ - name: Detect API URL override
27
+ if: github.event_name == 'pull_request'
28
+ id: api-url
29
+ run: |
30
+ if [ -f .api-url ]; then
31
+ url=$(cat .api-url | tr -d '[:space:]')
32
+ if [ -n "$url" ]; then
33
+ echo "base_url=$url" >> "$GITHUB_OUTPUT"
34
+ fi
35
+ fi
23
36
 
24
37
  - name: Set up Python ${{ matrix.python-version }}
25
38
  uses: actions/setup-python@v5
@@ -40,7 +53,7 @@ jobs:
40
53
 
41
54
  - name: Run tests
42
55
  run: |
43
- PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com \
56
+ PDFDANCER_BASE_URL=${{ steps.api-url.outputs.base_url || 'https://api-staging.pdfdancer.com' }} \
44
57
  PDFDANCER_TOKEN=42 \
45
58
  venv/bin/python -m pytest tests/ -v --maxfail=3
46
59
 
@@ -64,6 +77,20 @@ jobs:
64
77
 
65
78
  steps:
66
79
  - uses: actions/checkout@v4
80
+ with:
81
+ submodules: true
82
+
83
+ - name: Detect API URL override
84
+ if: github.event_name == 'pull_request'
85
+ id: api-url
86
+ shell: bash
87
+ run: |
88
+ if [ -f .api-url ]; then
89
+ url=$(cat .api-url | tr -d '[:space:]')
90
+ if [ -n "$url" ]; then
91
+ echo "base_url=$url" >> "$GITHUB_OUTPUT"
92
+ fi
93
+ fi
67
94
 
68
95
  - name: Set up Python ${{ matrix.python-version }}
69
96
  uses: actions/setup-python@v5
@@ -88,7 +115,7 @@ jobs:
88
115
  - name: Run tests (Unix)
89
116
  if: runner.os != 'Windows'
90
117
  run: |
91
- PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com \
118
+ PDFDANCER_BASE_URL=${{ steps.api-url.outputs.base_url || 'https://api-staging.pdfdancer.com' }} \
92
119
  PDFDANCER_TOKEN=42 \
93
120
  venv/bin/python -m pytest tests/ -v --maxfail=3
94
121
 
@@ -119,7 +146,7 @@ jobs:
119
146
  if: runner.os == 'Windows'
120
147
  shell: cmd
121
148
  run: |
122
- set PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com
149
+ set PDFDANCER_BASE_URL=${{ steps.api-url.outputs.base_url || 'https://api-staging.pdfdancer.com' }}
123
150
  set PDFDANCER_TOKEN=42
124
151
  venv\Scripts\python -m pytest tests/ -v --maxfail=3
125
152
 
@@ -18,6 +18,8 @@ jobs:
18
18
 
19
19
  steps:
20
20
  - uses: actions/checkout@v4
21
+ with:
22
+ submodules: true
21
23
 
22
24
  - name: Set up Python ${{ matrix.python-version }}
23
25
  uses: actions/setup-python@v5
@@ -0,0 +1,84 @@
1
+ name: Release to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ with:
13
+ submodules: true
14
+
15
+ - name: Set up Python 3.12
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: '3.12'
19
+
20
+ - name: Create virtual environment
21
+ run: python -m venv venv
22
+
23
+ - name: Install dependencies
24
+ run: |
25
+ venv/bin/pip install --upgrade pip
26
+ venv/bin/pip install -e ".[dev]"
27
+
28
+ - name: Run linter
29
+ run: venv/bin/python -m flake8 src/
30
+
31
+ - name: Run tests
32
+ run: venv/bin/python -m pytest tests/ -v --maxfail=1 --ignore=tests/e2e/test_pdfdancer.py
33
+ env:
34
+ PDFDANCER_BASE_URL: https://api.pdfdancer.com
35
+ PDFDANCER_API_TOKEN: ${{ secrets.PDFDANCER_API_TOKEN_PRODUCTION }}
36
+
37
+ publish:
38
+ needs: test
39
+ runs-on: ubuntu-latest
40
+ permissions:
41
+ id-token: write
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+ with:
45
+ submodules: true
46
+
47
+ - name: Set up Python 3.12
48
+ uses: actions/setup-python@v5
49
+ with:
50
+ python-version: '3.12'
51
+
52
+ - name: Verify tag matches package version
53
+ run: |
54
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
55
+ PKG_VERSION=$(python -c "import re; print(re.search(r'version\s*=\s*\"([^\"]+)\"', open('pyproject.toml').read()).group(1))")
56
+ if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
57
+ echo "Tag version ($TAG_VERSION) does not match pyproject.toml version ($PKG_VERSION)"
58
+ exit 1
59
+ fi
60
+ echo "Version verified: $TAG_VERSION"
61
+
62
+ - name: Install build dependencies
63
+ run: pip install build
64
+
65
+ - name: Build package
66
+ run: python -m build
67
+
68
+ - name: Publish to PyPI
69
+ uses: pypa/gh-action-pypi-publish@release/v1
70
+
71
+ github-release:
72
+ needs: publish
73
+ runs-on: ubuntu-latest
74
+ permissions:
75
+ contents: write
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+ with:
79
+ submodules: true
80
+
81
+ - name: Create GitHub Release
82
+ run: gh release create "$GITHUB_REF_NAME" --generate-notes
83
+ env:
84
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -19,3 +19,5 @@ __pycache__/
19
19
  /.run/
20
20
  /build
21
21
  .env
22
+ .propagate*md
23
+ /.last-message.txt
@@ -0,0 +1,3 @@
1
+ [submodule "tests/fixtures/examples"]
2
+ path = tests/fixtures/examples
3
+ url = https://github.com/MenschMachine/pdfdancer-examples.git
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfdancer-client-python
3
- Version: 0.3.10
3
+ Version: 0.3.12
4
4
  Summary: Python client for PDFDancer API
5
5
  Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
6
6
  License:
@@ -210,7 +210,7 @@ Project-URL: Homepage, https://www.pdfdancer.com/
210
210
  Project-URL: Documentation, https://www.pdfdancer.com/
211
211
  Project-URL: Source, https://github.com/MenschMachine/pdfdancer-client-python
212
212
  Project-URL: Issues, https://github.com/MenschMachine/pdfdancer-client-python/issues
213
- Classifier: Development Status :: 4 - Beta
213
+ Classifier: Development Status :: 5 - Production/Stable
214
214
  Classifier: Intended Audience :: Developers
215
215
  Classifier: License :: OSI Approved :: Apache Software License
216
216
  Classifier: Programming Language :: Python :: 3.10
@@ -359,7 +359,7 @@ with PDFDancer.open("contract.pdf") as pdf:
359
359
  ```
360
360
 
361
361
  Selectors return typed objects (`ParagraphObject`, `TextLineObject`, `ImageObject`, `FormFieldObject`, `PageClient`, …)
362
- with helpers such as `delete()`, `move_to(x, y)`, `redact()`, or `edit()` depending on the object type.
362
+ with helpers such as `delete()`, `move_to(x, y)`, `clear_clipping()`, `redact()`, or `edit()` depending on the object type.
363
363
 
364
364
  **Singular selection methods** return the first match (or `None`) for convenience:
365
365
 
@@ -571,15 +571,16 @@ Artifacts will be created in the `dist/` directory.
571
571
 
572
572
  #### Publishing to PyPI
573
573
 
574
- ```bash
575
- # Test upload to TestPyPI (recommended first)
576
- python -m twine upload --repository testpypi dist/*
574
+ Releases are published automatically to PyPI when a `v*` tag is pushed to GitHub (via GitHub Actions with Trusted Publishers).
577
575
 
578
- # Upload to PyPI
579
- python -m twine upload dist/*
576
+ ```bash
577
+ # Set version, commit, and push tag — GitHub Actions handles the rest
578
+ python release.py tag --version 1.1.0
580
579
 
581
- # Or use the release script
582
- python release.py
580
+ # Or manually:
581
+ # 1. Update version in pyproject.toml and src/pdfdancer/__init__.py
582
+ # 2. Commit
583
+ # 3. git tag v1.1.0 && git push origin HEAD && git push origin v1.1.0
583
584
  ```
584
585
 
585
586
  #### Code Quality
@@ -120,7 +120,7 @@ with PDFDancer.open("contract.pdf") as pdf:
120
120
  ```
121
121
 
122
122
  Selectors return typed objects (`ParagraphObject`, `TextLineObject`, `ImageObject`, `FormFieldObject`, `PageClient`, …)
123
- with helpers such as `delete()`, `move_to(x, y)`, `redact()`, or `edit()` depending on the object type.
123
+ with helpers such as `delete()`, `move_to(x, y)`, `clear_clipping()`, `redact()`, or `edit()` depending on the object type.
124
124
 
125
125
  **Singular selection methods** return the first match (or `None`) for convenience:
126
126
 
@@ -332,15 +332,16 @@ Artifacts will be created in the `dist/` directory.
332
332
 
333
333
  #### Publishing to PyPI
334
334
 
335
- ```bash
336
- # Test upload to TestPyPI (recommended first)
337
- python -m twine upload --repository testpypi dist/*
335
+ Releases are published automatically to PyPI when a `v*` tag is pushed to GitHub (via GitHub Actions with Trusted Publishers).
338
336
 
339
- # Upload to PyPI
340
- python -m twine upload dist/*
337
+ ```bash
338
+ # Set version, commit, and push tag — GitHub Actions handles the rest
339
+ python release.py tag --version 1.1.0
341
340
 
342
- # Or use the release script
343
- python release.py
341
+ # Or manually:
342
+ # 1. Update version in pyproject.toml and src/pdfdancer/__init__.py
343
+ # 2. Commit
344
+ # 3. git tag v1.1.0 && git push origin HEAD && git push origin v1.1.0
344
345
  ```
345
346
 
346
347
  #### Code Quality
@@ -0,0 +1,61 @@
1
+ # Clear Clipping Capability
2
+
3
+ Most of the PDF model classes including container classes like PDFParagraph/PDFTextLine/PDFPathGroup now implement ClippingDetachable with the method clearClipping().
4
+ This removes any clipping path which was active for this element.
5
+
6
+ This is useful in case clients want to move an element but the new position is hidden by the clipping path. clearing it makes the element visible again.
7
+
8
+ The new backend is version 1.8.6-rc2, available in the local m2 repository
9
+
10
+ ## Test PDFs to use
11
+
12
+ examples/clipping/invisible-content-clipping-test.pdf
13
+
14
+ - A PDF where content is present but not visible due to clipping paths that exclude the content areas. Contains an image clipped away by one clipping path and vector paths clipped away by another clipping path.
15
+
16
+
17
+ ## Api Docs
18
+
19
+ Explain for all elements and under the clipping-section.
20
+
21
+ ## Website and Marketing
22
+
23
+ Not to mention there.
24
+
25
+ ## What's new Newsletter
26
+
27
+ include
28
+
29
+ ## Changelog
30
+
31
+ include
32
+
33
+ ## Implementation in pdfdancer-api
34
+
35
+ - Updated backend dependency to `com.tfc.pdf:pdfdancer-backend:1.8.6-rc2` in `build.gradle.kts` to use the clipping-detach support.
36
+ - Added client-facing helpers:
37
+ - `BaseReference.clearClipping()` for any object reference implementing clipping detach semantics via the API.
38
+ - `PathGroupReference.clearClipping()` for grouped vector paths.
39
+ - `PDFDancer.clearClipping(ObjectRef)` calling `PUT /pdf/clipping/clear`.
40
+ - `PDFDancer.clearPathGroupClipping(pageIndex, groupId)` calling `PUT /pdf/path-group/clipping/clear`.
41
+ - Both client calls invalidate local snapshot caches after mutation.
42
+ - Added server endpoints in both controllers:
43
+ - `PDFController` and `PDFControllerV1` expose `PUT /pdf/clipping/clear` and `PUT /pdf/path-group/clipping/clear`.
44
+ - V1 uses `ClearClippingRequestV1` and `ClearPathGroupClippingRequestV1`, converting both to internal requests via `toInternal()`.
45
+ - Added controller orchestration in `ControllerOps`:
46
+ - `clearClipping(...)` validates `objectRef`, executes `ClearObjectClippingCommand`, and publishes `PDF_OBJECT_MODIFIED` metric with operation `clear_clipping`.
47
+ - `clearPathGroupClipping(...)` validates `groupId` and `pageIndex`, executes `ClearPathGroupClippingCommand`, and publishes `VECTOR_MANIPULATION` metric with operation `clear_path_group_clipping`.
48
+ - Wired session and replay support:
49
+ - `Session.clearClipping(...)` and `Session.clearPathGroupClipping(...)` execute commands inside `SessionContext` and record commands for session history.
50
+ - `CommandDeserializer` now reconstructs `ClearObjectClippingCommand` and `ClearPathGroupClippingCommand` for debug archive replay.
51
+ - Added tests and assertions:
52
+ - `ClippingTest` verifies clearing clipping on `PathReference`, `PathGroupReference`, and `TextLineReference`.
53
+ - `DirectPDFAssertions`/`PDFAssertions` gained helpers to detect clipped paths and assert clipping present/removed.
54
+
55
+ ## Implementation in pdfdancer-client-python
56
+
57
+ - Added top-level client methods in `src/pdfdancer/pdfdancer_v1.py`: `PDFDancer.clear_clipping(object_ref)` calls `PUT /pdf/clipping/clear`, and `PDFDancer.clear_path_group_clipping(page_number, group_id)` calls `PUT /pdf/path-group/clipping/clear`.
58
+ - The Python client validates required inputs before sending the request: `object_ref` must be present, `page_number` is a 1-based integer for path groups, and `group_id` must be non-empty. Both methods coerce the API response to `bool` and invalidate cached snapshots after a successful mutation.
59
+ - Added object-level convenience methods in `src/pdfdancer/types.py`. `PDFObjectBase.clear_clipping()` makes the capability available on typed selections that expose `object_ref()` such as paths, images, and text lines, while `PathGroupObject.clear_clipping()` forwards to the path-group API using `self._page_index + 1` to convert the internal 0-based index to the public 1-based page number.
60
+ - Updated `README.md` to list `clear_clipping()` alongside other typed-object helper methods so the feature is discoverable from the main usage guide.
61
+ - Added end-to-end coverage in `tests/e2e/test_clipping.py` for direct object calls and top-level client calls on paths, grouped paths, images, and clipped text lines, including a case where clipping spans multiple content streams. `tests/e2e/pdf_assertions.py` was extended with PDF draw-event inspection helpers that assert whether clipping is present or removed.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pdfdancer-client-python"
7
- version = "0.3.10"
7
+ version = "0.3.12"
8
8
  description = "Python client for PDFDancer API"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -12,7 +12,7 @@ authors = [
12
12
  ]
13
13
  license = { file = "LICENSE" }
14
14
  classifiers = [
15
- "Development Status :: 4 - Beta",
15
+ "Development Status :: 5 - Production/Stable",
16
16
  "Intended Audience :: Developers",
17
17
  "License :: OSI Approved :: Apache Software License",
18
18
  "Programming Language :: Python :: 3.10",
@@ -20,10 +20,12 @@ class ReleaseError(Exception):
20
20
 
21
21
 
22
22
  class VersionBumper:
23
- """Handles version bumping in pyproject.toml."""
23
+ """Handles version bumping in pyproject.toml and __init__.py."""
24
24
 
25
- def __init__(self, pyproject_path: Path = Path("pyproject.toml")):
25
+ def __init__(self, pyproject_path: Path = Path("pyproject.toml"),
26
+ init_path: Path = Path("src/pdfdancer/__init__.py")):
26
27
  self.pyproject_path = pyproject_path
28
+ self.init_path = init_path
27
29
  if not self.pyproject_path.exists():
28
30
  raise ReleaseError(f"pyproject.toml not found at {pyproject_path}")
29
31
 
@@ -36,7 +38,8 @@ class VersionBumper:
36
38
  return match.group(1)
37
39
 
38
40
  def set_version(self, new_version: str) -> None:
39
- """Set a new version in pyproject.toml."""
41
+ """Set a new version in pyproject.toml and __init__.py."""
42
+ # Update pyproject.toml
40
43
  content = self.pyproject_path.read_text()
41
44
  new_content = re.sub(
42
45
  r'^version\s*=\s*"[^"]+"',
@@ -48,6 +51,18 @@ class VersionBumper:
48
51
  raise ReleaseError("Failed to update version in pyproject.toml")
49
52
  self.pyproject_path.write_text(new_content)
50
53
 
54
+ # Update __init__.py
55
+ if self.init_path.exists():
56
+ init_content = self.init_path.read_text()
57
+ new_init = re.sub(
58
+ r'^__version__\s*=\s*"[^"]+"',
59
+ f'__version__ = "{new_version}"',
60
+ init_content,
61
+ flags=re.MULTILINE
62
+ )
63
+ self.init_path.write_text(new_init)
64
+ print(f"Updated {self.init_path} to {new_version}")
65
+
51
66
  def bump_version(self, bump_type: str) -> str:
52
67
  """Bump version by type (major, minor, patch)."""
53
68
  current = self.get_current_version()
@@ -159,8 +174,8 @@ def main():
159
174
  parser = argparse.ArgumentParser(description="PDFDancer Python Client Release Tool")
160
175
  parser.add_argument(
161
176
  "action",
162
- choices=["bump", "upload", "release"],
163
- help="Action to perform: bump (version only), upload (build+upload), release (bump+test+build+upload)"
177
+ choices=["bump", "upload", "release", "tag"],
178
+ help="Action to perform: bump (version only), upload (build+upload), release (bump+test+build+upload), tag (set version + commit + push tag)"
164
179
  )
165
180
  parser.add_argument(
166
181
  "--bump-type",
@@ -230,6 +245,37 @@ def main():
230
245
 
231
246
  print(f"New version: {new_version}")
232
247
 
248
+ if args.action == "tag":
249
+ if not args.version:
250
+ raise ReleaseError("--version is required for the tag action")
251
+
252
+ new_version = args.version
253
+ current_version = version_bumper.get_current_version()
254
+ print(f"Current version: {current_version}")
255
+
256
+ if not args.dry_run:
257
+ version_bumper.set_version(new_version)
258
+ print(f"Version set to {new_version}")
259
+
260
+ # Commit the version bump
261
+ uploader.run_command(
262
+ ["git", "add", "pyproject.toml", "src/pdfdancer/__init__.py"]
263
+ )
264
+ uploader.run_command(
265
+ ["git", "commit", "-m", f"release: v{new_version}"]
266
+ )
267
+ print(f"Committed version bump")
268
+
269
+ # Create and push tag
270
+ tag_name = f"v{new_version}"
271
+ uploader.run_command(["git", "tag", tag_name])
272
+ uploader.run_command(["git", "push", "origin", "HEAD"])
273
+ uploader.run_command(["git", "push", "origin", tag_name])
274
+ print(f"Pushed tag {tag_name}")
275
+ else:
276
+ print(f"Would set version to {new_version}")
277
+ print(f"Would commit and push tag v{new_version}")
278
+
233
279
  if args.action in ["upload", "release"]:
234
280
  if args.action == "release" and not args.skip_tests:
235
281
  if not args.dry_run:
@@ -12,6 +12,7 @@ from .exceptions import (
12
12
  PdfDancerException,
13
13
  RateLimitException,
14
14
  SessionException,
15
+ SessionNotFoundException,
15
16
  ValidationException,
16
17
  )
17
18
  from .models import (
@@ -34,6 +35,7 @@ from .models import (
34
35
  PageSize,
35
36
  Paragraph,
36
37
  Path,
38
+ PathGroupInfo,
37
39
  PathSegment,
38
40
  Point,
39
41
  Position,
@@ -47,12 +49,13 @@ from .models import (
47
49
  TextObjectRef,
48
50
  TextStatus,
49
51
  )
52
+ from .types import PathGroupObject
50
53
  from .page_builder import PageBuilder
51
54
  from .paragraph_builder import ParagraphBuilder
52
55
  from .path_builder import BezierBuilder, LineBuilder, PathBuilder
53
56
  from .text_line_builder import TextLineBuilder
54
57
 
55
- __version__ = "1.0.0"
58
+ __version__ = "0.3.12"
56
59
  __all__ = [
57
60
  "PDFDancer",
58
61
  "ParagraphBuilder",
@@ -89,6 +92,8 @@ __all__ = [
89
92
  "Line",
90
93
  "Bezier",
91
94
  "Path",
95
+ "PathGroupInfo",
96
+ "PathGroupObject",
92
97
  "RedactTarget",
93
98
  "RedactResponse",
94
99
  "ReflowPreset",
@@ -97,6 +102,7 @@ __all__ = [
97
102
  "ValidationException",
98
103
  "HttpClientException",
99
104
  "SessionException",
105
+ "SessionNotFoundException",
100
106
  "RateLimitException",
101
107
  "set_ssl_verify",
102
108
  ]
@@ -55,6 +55,15 @@ class SessionException(PdfDancerException):
55
55
  pass
56
56
 
57
57
 
58
+ class SessionNotFoundException(SessionException):
59
+ """
60
+ Exception raised when a session is not found (expired or invalid).
61
+ """
62
+
63
+ def __init__(self, message: str):
64
+ super().__init__(message)
65
+
66
+
58
67
  class ValidationException(PdfDancerException):
59
68
  """
60
69
  Exception raised for input validation errors.
@@ -1643,6 +1643,25 @@ class Size:
1643
1643
  return {"width": self.width, "height": self.height}
1644
1644
 
1645
1645
 
1646
+ @dataclass
1647
+ class PathGroupInfo:
1648
+ group_id: str
1649
+ path_count: int
1650
+ bounding_box: Optional[Dict[str, Any]]
1651
+ x: float
1652
+ y: float
1653
+
1654
+ @staticmethod
1655
+ def from_dict(d: Dict[str, Any]) -> "PathGroupInfo":
1656
+ return PathGroupInfo(
1657
+ group_id=d.get("groupId", ""),
1658
+ path_count=d.get("pathCount", 0),
1659
+ bounding_box=d.get("boundingBox"),
1660
+ x=d.get("x", 0.0),
1661
+ y=d.get("y", 0.0),
1662
+ )
1663
+
1664
+
1646
1665
  class ImageTransformType(Enum):
1647
1666
  """Type of image transformation operation."""
1648
1667