pdfdancer-client-python 0.3.9__tar.gz → 0.3.11__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.11/.github/workflows/release.yml +78 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/.gitignore +1 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/PKG-INFO +11 -9
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/README.md +9 -7
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/pyproject.toml +2 -2
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/release.py +51 -5
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/__init__.py +7 -1
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/exceptions.py +9 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/models.py +39 -2
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/pdfdancer_v1.py +154 -3
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/types.py +56 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer_client_python.egg-info/PKG-INFO +11 -9
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer_client_python.egg-info/SOURCES.txt +3 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/__init__.py +2 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/pdf_assertions.py +25 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_acroform.py +2 -2
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_form_x_objects.py +1 -1
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_image.py +3 -3
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_line.py +1 -1
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_paragraph.py +9 -9
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_path.py +5 -5
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_path_comprehensive.py +1 -1
- pdfdancer_client_python-0.3.11/tests/e2e/test_path_group.py +244 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_redact.py +2 -2
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_template_replace.py +53 -1
- pdfdancer_client_python-0.3.11/tests/e2e/test_template_replace_linebreak.py +141 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/test_models.py +95 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/.claude/commands/discuss.md +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/.claude/commands/implement-new-api-features.md +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/.flake8 +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/.github/workflows/ci.yml +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/.github/workflows/daily-tests.yml +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/CLAUDE.md +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/LICENSE +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/NOTICE +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/TODO.md +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/check.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/docs/api-schemas/v0.yml +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/docs/api-schemas/v1.yml +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/media/logo-orange-512h.webp +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/media/logo-orange-60h.webp +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/media/logo-silver-512h.webp +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/media/logo-silver-60h.webp +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/setup.cfg +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/fingerprint.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/image_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/page_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/paragraph_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/path_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/text_line_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/test.sh +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/__init__.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/conftest.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_bezier_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_context_manager.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_image_transform.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_line_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_new_pdf.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_page.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_path_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_path_builder_rectangle.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_pdfdancer.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_positioning.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_rectangle_builder.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_singular_selection.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_snapshot.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/e2e/test_text_line_edit.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/fixtures/DancingScript-Regular.ttf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/fixtures/Empty.pdf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/fixtures/Showcase.pdf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/fixtures/basic-paths.pdf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/fixtures/form-xobject-example.pdf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/fixtures/logo-80.png +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/fixtures/mixed-form-types.pdf +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/test_anonymous_token.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/test_fingerprint.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/test_openapi_compliance.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/test_path_models.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/test_pdf_object_equality.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/test_rate_limit.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/tests/test_standard_fonts.py +0 -0
- {pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/update-api-spec.sh +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
|
|
13
|
+
- name: Set up Python 3.12
|
|
14
|
+
uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: '3.12'
|
|
17
|
+
|
|
18
|
+
- name: Create virtual environment
|
|
19
|
+
run: python -m venv venv
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: |
|
|
23
|
+
venv/bin/pip install --upgrade pip
|
|
24
|
+
venv/bin/pip install -e ".[dev]"
|
|
25
|
+
|
|
26
|
+
- name: Run linter
|
|
27
|
+
run: venv/bin/python -m flake8 src/
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: venv/bin/python -m pytest tests/ -v --maxfail=1 --ignore=tests/e2e/test_pdfdancer.py
|
|
31
|
+
env:
|
|
32
|
+
PDFDANCER_BASE_URL: https://api.pdfdancer.com
|
|
33
|
+
PDFDANCER_API_TOKEN: ${{ secrets.PDFDANCER_API_TOKEN_PRODUCTION }}
|
|
34
|
+
|
|
35
|
+
publish:
|
|
36
|
+
needs: test
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
permissions:
|
|
39
|
+
id-token: write
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
|
|
43
|
+
- name: Set up Python 3.12
|
|
44
|
+
uses: actions/setup-python@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: '3.12'
|
|
47
|
+
|
|
48
|
+
- name: Verify tag matches package version
|
|
49
|
+
run: |
|
|
50
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
51
|
+
PKG_VERSION=$(python -c "import re; print(re.search(r'version\s*=\s*\"([^\"]+)\"', open('pyproject.toml').read()).group(1))")
|
|
52
|
+
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
|
|
53
|
+
echo "Tag version ($TAG_VERSION) does not match pyproject.toml version ($PKG_VERSION)"
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
echo "Version verified: $TAG_VERSION"
|
|
57
|
+
|
|
58
|
+
- name: Install build dependencies
|
|
59
|
+
run: pip install build
|
|
60
|
+
|
|
61
|
+
- name: Build package
|
|
62
|
+
run: python -m build
|
|
63
|
+
|
|
64
|
+
- name: Publish to PyPI
|
|
65
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
66
|
+
|
|
67
|
+
github-release:
|
|
68
|
+
needs: publish
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
permissions:
|
|
71
|
+
contents: write
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@v4
|
|
74
|
+
|
|
75
|
+
- name: Create GitHub Release
|
|
76
|
+
run: gh release create "$GITHUB_REF_NAME" --generate-notes
|
|
77
|
+
env:
|
|
78
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdfdancer-client-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.11
|
|
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 ::
|
|
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
|
|
@@ -571,15 +571,16 @@ Artifacts will be created in the `dist/` directory.
|
|
|
571
571
|
|
|
572
572
|
#### Publishing to PyPI
|
|
573
573
|
|
|
574
|
-
|
|
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
|
-
|
|
579
|
-
|
|
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
|
|
582
|
-
|
|
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
|
|
@@ -672,6 +673,7 @@ Contributions are welcome via pull request. Please:
|
|
|
672
673
|
- [PyPI](https://pypi.org/project/pdfdancer-client-python/)
|
|
673
674
|
- [Changelog](https://www.pdfdancer.com/changelog/?utm_source=github&utm_medium=readme&utm_campaign=pdfdancer-python)
|
|
674
675
|
- [Status](https://status.pdfdancer.com?utm_source=github&utm_medium=readme&utm_campaign=pdfdancer-python)
|
|
676
|
+
- [Issue tracker](https://github.com/MenschMachine/pdfdancer)
|
|
675
677
|
|
|
676
678
|
## Related SDKs
|
|
677
679
|
|
|
@@ -332,15 +332,16 @@ Artifacts will be created in the `dist/` directory.
|
|
|
332
332
|
|
|
333
333
|
#### Publishing to PyPI
|
|
334
334
|
|
|
335
|
-
|
|
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
|
-
|
|
340
|
-
|
|
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
|
|
343
|
-
|
|
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
|
|
@@ -433,6 +434,7 @@ Contributions are welcome via pull request. Please:
|
|
|
433
434
|
- [PyPI](https://pypi.org/project/pdfdancer-client-python/)
|
|
434
435
|
- [Changelog](https://www.pdfdancer.com/changelog/?utm_source=github&utm_medium=readme&utm_campaign=pdfdancer-python)
|
|
435
436
|
- [Status](https://status.pdfdancer.com?utm_source=github&utm_medium=readme&utm_campaign=pdfdancer-python)
|
|
437
|
+
- [Issue tracker](https://github.com/MenschMachine/pdfdancer)
|
|
436
438
|
|
|
437
439
|
## Related SDKs
|
|
438
440
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pdfdancer-client-python"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.11"
|
|
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 ::
|
|
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__ = "
|
|
58
|
+
__version__ = "0.3.11"
|
|
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
|
]
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/exceptions.py
RENAMED
|
@@ -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
|
|
|
@@ -1686,18 +1705,22 @@ class TemplateReplacement:
|
|
|
1686
1705
|
|
|
1687
1706
|
Parameters:
|
|
1688
1707
|
- placeholder: The exact text to find and replace in the PDF.
|
|
1689
|
-
- text: The text to replace the placeholder with.
|
|
1708
|
+
- text: The text to replace the placeholder with. None for image replacements.
|
|
1690
1709
|
- font: Optional font for the replacement text.
|
|
1691
1710
|
- color: Optional color for the replacement text.
|
|
1711
|
+
- image: Optional Image to replace the placeholder with. When set, text should be None.
|
|
1692
1712
|
"""
|
|
1693
1713
|
|
|
1694
1714
|
placeholder: str
|
|
1695
|
-
text: str
|
|
1715
|
+
text: Optional[str] = None
|
|
1696
1716
|
font: Optional[Font] = None
|
|
1697
1717
|
color: Optional[Color] = None
|
|
1718
|
+
image: Optional[Image] = None
|
|
1698
1719
|
|
|
1699
1720
|
def to_dict(self) -> dict:
|
|
1700
1721
|
"""Convert to dictionary for JSON serialization."""
|
|
1722
|
+
import base64
|
|
1723
|
+
|
|
1701
1724
|
result: Dict[str, Any] = {
|
|
1702
1725
|
"placeholder": self.placeholder,
|
|
1703
1726
|
"text": self.text,
|
|
@@ -1711,6 +1734,20 @@ class TemplateReplacement:
|
|
|
1711
1734
|
"blue": self.color.b,
|
|
1712
1735
|
"alpha": self.color.a,
|
|
1713
1736
|
}
|
|
1737
|
+
if self.image:
|
|
1738
|
+
image_dict: Dict[str, Any] = {}
|
|
1739
|
+
if self.image.data:
|
|
1740
|
+
image_dict["data"] = base64.b64encode(self.image.data).decode("utf-8")
|
|
1741
|
+
if self.image.format:
|
|
1742
|
+
image_dict["format"] = self.image.format
|
|
1743
|
+
if self.image.width is not None or self.image.height is not None:
|
|
1744
|
+
size: Dict[str, float] = {}
|
|
1745
|
+
if self.image.width is not None:
|
|
1746
|
+
size["width"] = self.image.width
|
|
1747
|
+
if self.image.height is not None:
|
|
1748
|
+
size["height"] = self.image.height
|
|
1749
|
+
image_dict["size"] = size
|
|
1750
|
+
result["image"] = image_dict
|
|
1714
1751
|
return result
|
|
1715
1752
|
|
|
1716
1753
|
|
{pdfdancer_client_python-0.3.9 → pdfdancer_client_python-0.3.11}/src/pdfdancer/pdfdancer_v1.py
RENAMED
|
@@ -19,7 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
from typing import TYPE_CHECKING, Any, BinaryIO, Dict, List, Mapping, Optional, Union
|
|
20
20
|
|
|
21
21
|
import httpx
|
|
22
|
-
from dotenv import load_dotenv
|
|
22
|
+
from dotenv import find_dotenv, load_dotenv
|
|
23
23
|
|
|
24
24
|
from . import BezierBuilder, LineBuilder, ParagraphBuilder, PathBuilder
|
|
25
25
|
from .exceptions import (
|
|
@@ -28,6 +28,7 @@ from .exceptions import (
|
|
|
28
28
|
PdfDancerException,
|
|
29
29
|
RateLimitException,
|
|
30
30
|
SessionException,
|
|
31
|
+
SessionNotFoundException,
|
|
31
32
|
ValidationException,
|
|
32
33
|
)
|
|
33
34
|
from .fingerprint import Fingerprint
|
|
@@ -85,7 +86,16 @@ if TYPE_CHECKING:
|
|
|
85
86
|
from .models import ImageTransformRequest, PathSegment
|
|
86
87
|
from .path_builder import RectangleBuilder
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
_env_loaded = False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _load_env():
|
|
93
|
+
global _env_loaded
|
|
94
|
+
if _env_loaded:
|
|
95
|
+
return
|
|
96
|
+
load_dotenv(find_dotenv(usecwd=True))
|
|
97
|
+
_env_loaded = True
|
|
98
|
+
|
|
89
99
|
|
|
90
100
|
# Client identifier header for all HTTP requests
|
|
91
101
|
# Always reads from installed package metadata (stays in sync with pyproject.toml)
|
|
@@ -131,6 +141,34 @@ def _dict_to_replacements(
|
|
|
131
141
|
for placeholder, value in replacements.items():
|
|
132
142
|
if isinstance(value, str):
|
|
133
143
|
result.append(TemplateReplacement(placeholder=placeholder, text=value))
|
|
144
|
+
elif "image" in value:
|
|
145
|
+
image_source = value["image"]
|
|
146
|
+
if isinstance(image_source, Path):
|
|
147
|
+
image_data = image_source.read_bytes()
|
|
148
|
+
image_format = image_source.suffix.lstrip(".").upper()
|
|
149
|
+
if image_format == "JPG":
|
|
150
|
+
image_format = "JPEG"
|
|
151
|
+
elif isinstance(image_source, bytes):
|
|
152
|
+
image_data = image_source
|
|
153
|
+
image_format = None
|
|
154
|
+
else:
|
|
155
|
+
raise ValueError(
|
|
156
|
+
f"Unsupported image source type: {type(image_source)}. "
|
|
157
|
+
"Use a Path or bytes."
|
|
158
|
+
)
|
|
159
|
+
img = Image(
|
|
160
|
+
data=image_data,
|
|
161
|
+
format=value.get("format", image_format),
|
|
162
|
+
width=value.get("width"),
|
|
163
|
+
height=value.get("height"),
|
|
164
|
+
)
|
|
165
|
+
result.append(
|
|
166
|
+
TemplateReplacement(
|
|
167
|
+
placeholder=placeholder,
|
|
168
|
+
text=None,
|
|
169
|
+
image=img,
|
|
170
|
+
)
|
|
171
|
+
)
|
|
134
172
|
else:
|
|
135
173
|
result.append(
|
|
136
174
|
TemplateReplacement(
|
|
@@ -644,6 +682,8 @@ class PageClient:
|
|
|
644
682
|
replacements: Dict mapping placeholder strings to replacement values.
|
|
645
683
|
- Simple: {"{{NAME}}": "John Doe"}
|
|
646
684
|
- With options: {"{{NAME}}": {"text": "John", "font": Font(...), "color": Color(...)}}
|
|
685
|
+
- With image: {"{{LOGO}}": {"image": Path("logo.png")}}
|
|
686
|
+
- With image and size: {"{{LOGO}}": {"image": Path("logo.png"), "width": 50, "height": 50}}
|
|
647
687
|
reflow_preset: Optional ReflowPreset to control text reflow behavior.
|
|
648
688
|
- BEST_EFFORT: Attempt to reflow, proceed even if imperfect
|
|
649
689
|
- FIT_OR_FAIL: Reflow must succeed or operation fails
|
|
@@ -698,6 +738,28 @@ class PageClient:
|
|
|
698
738
|
self.root._find_paths(Position.at_page(self.page_number))
|
|
699
739
|
)
|
|
700
740
|
|
|
741
|
+
def group_paths(self, path_ids):
|
|
742
|
+
"""Group paths by their IDs. Returns a PathGroupObject."""
|
|
743
|
+
from .types import PathGroupObject
|
|
744
|
+
|
|
745
|
+
page_index = self.page_number - 1
|
|
746
|
+
info = self.root._create_path_group(page_index, path_ids=path_ids)
|
|
747
|
+
return PathGroupObject(self.root, page_index, info)
|
|
748
|
+
|
|
749
|
+
def group_paths_in_region(self, region):
|
|
750
|
+
"""Group paths within a bounding region. Returns a PathGroupObject."""
|
|
751
|
+
from .types import PathGroupObject
|
|
752
|
+
|
|
753
|
+
page_index = self.page_number - 1
|
|
754
|
+
info = self.root._create_path_group(page_index, region=region)
|
|
755
|
+
return PathGroupObject(self.root, page_index, info)
|
|
756
|
+
|
|
757
|
+
def get_path_groups(self):
|
|
758
|
+
"""List all path groups on this page."""
|
|
759
|
+
|
|
760
|
+
page_index = self.page_number - 1
|
|
761
|
+
return self.root._list_path_groups(page_index)
|
|
762
|
+
|
|
701
763
|
def select_elements(self):
|
|
702
764
|
"""
|
|
703
765
|
Select all elements (paragraphs, images, paths, forms) on this page.
|
|
@@ -789,6 +851,7 @@ class PDFDancer:
|
|
|
789
851
|
|
|
790
852
|
@classmethod
|
|
791
853
|
def _resolve_base_url(cls, base_url: Optional[str]) -> Optional[str]:
|
|
854
|
+
_load_env()
|
|
792
855
|
env_base_url = os.getenv("PDFDANCER_BASE_URL")
|
|
793
856
|
resolved_base_url = base_url or (
|
|
794
857
|
env_base_url.strip() if env_base_url and env_base_url.strip() else None
|
|
@@ -923,6 +986,7 @@ class PDFDancer:
|
|
|
923
986
|
1. PDFDANCER_API_TOKEN (preferred)
|
|
924
987
|
2. PDFDANCER_TOKEN (legacy)
|
|
925
988
|
"""
|
|
989
|
+
_load_env()
|
|
926
990
|
resolved_token = token.strip() if token and token.strip() else None
|
|
927
991
|
if resolved_token is None:
|
|
928
992
|
# Check PDFDANCER_API_TOKEN first (preferred), then PDFDANCER_TOKEN (legacy)
|
|
@@ -1586,7 +1650,7 @@ class PDFDancer:
|
|
|
1586
1650
|
|
|
1587
1651
|
_log_generated_at_header(response, method, path)
|
|
1588
1652
|
|
|
1589
|
-
# Handle
|
|
1653
|
+
# Handle 404 errors
|
|
1590
1654
|
if response.status_code == 404:
|
|
1591
1655
|
try:
|
|
1592
1656
|
error_data = response.json()
|
|
@@ -1594,6 +1658,10 @@ class PDFDancer:
|
|
|
1594
1658
|
raise FontNotFoundException(
|
|
1595
1659
|
error_data.get("message", "Font not found")
|
|
1596
1660
|
)
|
|
1661
|
+
if error_data.get("error") == "SessionNotFoundException":
|
|
1662
|
+
raise SessionNotFoundException(
|
|
1663
|
+
error_data.get("message", "Session not found")
|
|
1664
|
+
)
|
|
1597
1665
|
except (json.JSONDecodeError, KeyError):
|
|
1598
1666
|
pass
|
|
1599
1667
|
|
|
@@ -2281,6 +2349,8 @@ class PDFDancer:
|
|
|
2281
2349
|
replacements: Dict mapping placeholder strings to replacement values.
|
|
2282
2350
|
- Simple: {"{{NAME}}": "John Doe"}
|
|
2283
2351
|
- With options: {"{{NAME}}": {"text": "John", "font": Font(...), "color": Color(...)}}
|
|
2352
|
+
- With image: {"{{LOGO}}": {"image": Path("logo.png")}}
|
|
2353
|
+
- With image and size: {"{{LOGO}}": {"image": Path("logo.png"), "width": 50, "height": 50}}
|
|
2284
2354
|
reflow_preset: Optional ReflowPreset to control text reflow behavior.
|
|
2285
2355
|
- BEST_EFFORT: Attempt to reflow, proceed even if imperfect
|
|
2286
2356
|
- FIT_OR_FAIL: Reflow must succeed or operation fails
|
|
@@ -2423,6 +2493,87 @@ class PDFDancer:
|
|
|
2423
2493
|
|
|
2424
2494
|
return result
|
|
2425
2495
|
|
|
2496
|
+
# Path Group Operations (internal, 0-based page_index)
|
|
2497
|
+
def _create_path_group(self, page_index, path_ids=None, region=None):
|
|
2498
|
+
from .models import PathGroupInfo
|
|
2499
|
+
|
|
2500
|
+
if path_ids is not None:
|
|
2501
|
+
if not isinstance(path_ids, list) or len(path_ids) == 0:
|
|
2502
|
+
raise ValidationException("path_ids must be a non-empty list")
|
|
2503
|
+
|
|
2504
|
+
data = {"pageIndex": page_index}
|
|
2505
|
+
if path_ids is not None:
|
|
2506
|
+
data["pathIds"] = path_ids
|
|
2507
|
+
if region is not None:
|
|
2508
|
+
data["region"] = {
|
|
2509
|
+
"x": region.x, "y": region.y,
|
|
2510
|
+
"width": region.width, "height": region.height,
|
|
2511
|
+
}
|
|
2512
|
+
response = self._make_request(
|
|
2513
|
+
"POST", "/pdf/path-group/create", data=data
|
|
2514
|
+
)
|
|
2515
|
+
self._invalidate_snapshots()
|
|
2516
|
+
return PathGroupInfo.from_dict(response.json())
|
|
2517
|
+
|
|
2518
|
+
def _move_path_group(self, page_index, group_id, x, y):
|
|
2519
|
+
data = {
|
|
2520
|
+
"pageIndex": page_index, "groupId": group_id,
|
|
2521
|
+
"x": x, "y": y,
|
|
2522
|
+
}
|
|
2523
|
+
response = self._make_request("PUT", "/pdf/path-group/move", data=data)
|
|
2524
|
+
self._invalidate_snapshots()
|
|
2525
|
+
return response.json()
|
|
2526
|
+
|
|
2527
|
+
def _transform_path_group(self, page_index, group_id, transform_type, **kwargs):
|
|
2528
|
+
data = {
|
|
2529
|
+
"pageIndex": page_index, "groupId": group_id,
|
|
2530
|
+
"transformType": transform_type,
|
|
2531
|
+
}
|
|
2532
|
+
data.update({k: v for k, v in kwargs.items() if v is not None})
|
|
2533
|
+
response = self._make_request(
|
|
2534
|
+
"PUT", "/pdf/path-group/transform", data=data
|
|
2535
|
+
)
|
|
2536
|
+
self._invalidate_snapshots()
|
|
2537
|
+
return response.json()
|
|
2538
|
+
|
|
2539
|
+
def _scale_path_group(self, page_index, group_id, factor):
|
|
2540
|
+
if factor <= 0:
|
|
2541
|
+
raise ValidationException("Scale factor must be positive")
|
|
2542
|
+
return self._transform_path_group(
|
|
2543
|
+
page_index, group_id, "SCALE", scaleFactor=factor
|
|
2544
|
+
)
|
|
2545
|
+
|
|
2546
|
+
def _rotate_path_group(self, page_index, group_id, degrees):
|
|
2547
|
+
return self._transform_path_group(
|
|
2548
|
+
page_index, group_id, "ROTATE", rotationAngle=degrees
|
|
2549
|
+
)
|
|
2550
|
+
|
|
2551
|
+
def _resize_path_group(self, page_index, group_id, width, height):
|
|
2552
|
+
if width <= 0 or height <= 0:
|
|
2553
|
+
raise ValidationException("Width and height must be positive")
|
|
2554
|
+
return self._transform_path_group(
|
|
2555
|
+
page_index, group_id, "RESIZE",
|
|
2556
|
+
targetWidth=width, targetHeight=height,
|
|
2557
|
+
)
|
|
2558
|
+
|
|
2559
|
+
def _remove_path_group(self, page_index, group_id):
|
|
2560
|
+
data = {"pageIndex": page_index, "groupId": group_id}
|
|
2561
|
+
response = self._make_request(
|
|
2562
|
+
"DELETE", "/pdf/path-group/remove", data=data
|
|
2563
|
+
)
|
|
2564
|
+
self._invalidate_snapshots()
|
|
2565
|
+
return response.json()
|
|
2566
|
+
|
|
2567
|
+
def _list_path_groups(self, page_index):
|
|
2568
|
+
from .models import PathGroupInfo
|
|
2569
|
+
from .types import PathGroupObject
|
|
2570
|
+
|
|
2571
|
+
response = self._make_request(
|
|
2572
|
+
"GET", f"/pdf/page/{page_index}/path-groups"
|
|
2573
|
+
)
|
|
2574
|
+
infos = [PathGroupInfo.from_dict(d) for d in response.json()]
|
|
2575
|
+
return [PathGroupObject(self, page_index, info) for info in infos]
|
|
2576
|
+
|
|
2426
2577
|
def new_paragraph(self) -> ParagraphBuilder:
|
|
2427
2578
|
return ParagraphBuilder(self)
|
|
2428
2579
|
|