pdfdancer-client-python 0.3.10__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.10 → pdfdancer_client_python-0.3.11}/PKG-INFO +10 -9
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/README.md +8 -7
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/pyproject.toml +2 -2
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/release.py +51 -5
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer/__init__.py +7 -1
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer/exceptions.py +9 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer/models.py +19 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer/pdfdancer_v1.py +122 -3
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer/types.py +56 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer_client_python.egg-info/PKG-INFO +10 -9
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer_client_python.egg-info/SOURCES.txt +3 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/pdf_assertions.py +25 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_acroform.py +2 -2
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_form_x_objects.py +1 -1
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_image.py +3 -3
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_paragraph.py +9 -9
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_path.py +5 -5
- {pdfdancer_client_python-0.3.10 → 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.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_redact.py +2 -2
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_template_replace.py +1 -1
- pdfdancer_client_python-0.3.11/tests/e2e/test_template_replace_linebreak.py +141 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/.claude/commands/discuss.md +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/.claude/commands/implement-new-api-features.md +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/.flake8 +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/.github/workflows/ci.yml +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/.github/workflows/daily-tests.yml +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/.gitignore +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/CLAUDE.md +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/LICENSE +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/NOTICE +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/TODO.md +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/check.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/docs/api-schemas/v0.yml +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/docs/api-schemas/v1.yml +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/media/logo-orange-512h.webp +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/media/logo-orange-60h.webp +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/media/logo-silver-512h.webp +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/media/logo-silver-60h.webp +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/setup.cfg +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer/fingerprint.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer/image_builder.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer/page_builder.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer/paragraph_builder.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer/path_builder.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer/text_line_builder.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/test.sh +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/__init__.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/conftest.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/__init__.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_bezier_builder.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_context_manager.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_image_transform.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_line.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_line_builder.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_new_pdf.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_page.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_path_builder.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_path_builder_rectangle.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_pdfdancer.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_positioning.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_rectangle_builder.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_singular_selection.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_snapshot.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/e2e/test_text_line_edit.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/fixtures/DancingScript-Regular.ttf +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/fixtures/Empty.pdf +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/fixtures/Showcase.pdf +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/fixtures/basic-paths.pdf +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/fixtures/form-xobject-example.pdf +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/fixtures/logo-80.png +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/fixtures/mixed-form-types.pdf +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/test_anonymous_token.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/test_fingerprint.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/test_models.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/test_openapi_compliance.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/test_path_models.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/test_pdf_object_equality.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/test_rate_limit.py +0 -0
- {pdfdancer_client_python-0.3.10 → pdfdancer_client_python-0.3.11}/tests/test_standard_fonts.py +0 -0
- {pdfdancer_client_python-0.3.10 → 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
|
|
@@ -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
|
|
@@ -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.10 → 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
|
|
{pdfdancer_client_python-0.3.10 → 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)
|
|
@@ -728,6 +738,28 @@ class PageClient:
|
|
|
728
738
|
self.root._find_paths(Position.at_page(self.page_number))
|
|
729
739
|
)
|
|
730
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
|
+
|
|
731
763
|
def select_elements(self):
|
|
732
764
|
"""
|
|
733
765
|
Select all elements (paragraphs, images, paths, forms) on this page.
|
|
@@ -819,6 +851,7 @@ class PDFDancer:
|
|
|
819
851
|
|
|
820
852
|
@classmethod
|
|
821
853
|
def _resolve_base_url(cls, base_url: Optional[str]) -> Optional[str]:
|
|
854
|
+
_load_env()
|
|
822
855
|
env_base_url = os.getenv("PDFDANCER_BASE_URL")
|
|
823
856
|
resolved_base_url = base_url or (
|
|
824
857
|
env_base_url.strip() if env_base_url and env_base_url.strip() else None
|
|
@@ -953,6 +986,7 @@ class PDFDancer:
|
|
|
953
986
|
1. PDFDANCER_API_TOKEN (preferred)
|
|
954
987
|
2. PDFDANCER_TOKEN (legacy)
|
|
955
988
|
"""
|
|
989
|
+
_load_env()
|
|
956
990
|
resolved_token = token.strip() if token and token.strip() else None
|
|
957
991
|
if resolved_token is None:
|
|
958
992
|
# Check PDFDANCER_API_TOKEN first (preferred), then PDFDANCER_TOKEN (legacy)
|
|
@@ -1616,7 +1650,7 @@ class PDFDancer:
|
|
|
1616
1650
|
|
|
1617
1651
|
_log_generated_at_header(response, method, path)
|
|
1618
1652
|
|
|
1619
|
-
# Handle
|
|
1653
|
+
# Handle 404 errors
|
|
1620
1654
|
if response.status_code == 404:
|
|
1621
1655
|
try:
|
|
1622
1656
|
error_data = response.json()
|
|
@@ -1624,6 +1658,10 @@ class PDFDancer:
|
|
|
1624
1658
|
raise FontNotFoundException(
|
|
1625
1659
|
error_data.get("message", "Font not found")
|
|
1626
1660
|
)
|
|
1661
|
+
if error_data.get("error") == "SessionNotFoundException":
|
|
1662
|
+
raise SessionNotFoundException(
|
|
1663
|
+
error_data.get("message", "Session not found")
|
|
1664
|
+
)
|
|
1627
1665
|
except (json.JSONDecodeError, KeyError):
|
|
1628
1666
|
pass
|
|
1629
1667
|
|
|
@@ -2455,6 +2493,87 @@ class PDFDancer:
|
|
|
2455
2493
|
|
|
2456
2494
|
return result
|
|
2457
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
|
+
|
|
2458
2577
|
def new_paragraph(self) -> ParagraphBuilder:
|
|
2459
2578
|
return ParagraphBuilder(self)
|
|
2460
2579
|
|
|
@@ -292,6 +292,62 @@ class ImageObject(PDFObjectBase):
|
|
|
292
292
|
)
|
|
293
293
|
|
|
294
294
|
|
|
295
|
+
class PathGroupObject:
|
|
296
|
+
"""Represents a group of vector paths that can be manipulated as a unit."""
|
|
297
|
+
|
|
298
|
+
def __init__(self, client: "PDFDancer", page_index: int, info):
|
|
299
|
+
self._client = client
|
|
300
|
+
self._page_index = page_index
|
|
301
|
+
self._info = info
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def group_id(self) -> str:
|
|
305
|
+
return self._info.group_id
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def path_count(self) -> int:
|
|
309
|
+
return self._info.path_count
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def bounding_box(self):
|
|
313
|
+
return self._info.bounding_box
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def x(self) -> float:
|
|
317
|
+
return self._info.x
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def y(self) -> float:
|
|
321
|
+
return self._info.y
|
|
322
|
+
|
|
323
|
+
def move_to(self, x: float, y: float) -> bool:
|
|
324
|
+
self._client._move_path_group(self._page_index, self.group_id, x, y)
|
|
325
|
+
return True
|
|
326
|
+
|
|
327
|
+
def scale(self, factor: float) -> bool:
|
|
328
|
+
self._client._scale_path_group(self._page_index, self.group_id, factor)
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
def rotate(self, degrees: float) -> bool:
|
|
332
|
+
self._client._rotate_path_group(
|
|
333
|
+
self._page_index, self.group_id, degrees
|
|
334
|
+
)
|
|
335
|
+
return True
|
|
336
|
+
|
|
337
|
+
def resize(self, width: float, height: float) -> bool:
|
|
338
|
+
self._client._resize_path_group(
|
|
339
|
+
self._page_index, self.group_id, width, height
|
|
340
|
+
)
|
|
341
|
+
return True
|
|
342
|
+
|
|
343
|
+
def remove(self) -> bool:
|
|
344
|
+
self._client._remove_path_group(self._page_index, self.group_id)
|
|
345
|
+
return True
|
|
346
|
+
|
|
347
|
+
def __repr__(self):
|
|
348
|
+
return f"PathGroupObject(group_id={self.group_id!r}, path_count={self.path_count}, page_index={self._page_index})"
|
|
349
|
+
|
|
350
|
+
|
|
295
351
|
class FormObject(PDFObjectBase):
|
|
296
352
|
def __eq__(self, other):
|
|
297
353
|
if not isinstance(other, FormObject):
|
|
@@ -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
|
|
@@ -14,6 +14,7 @@ update-api-spec.sh
|
|
|
14
14
|
.claude/commands/implement-new-api-features.md
|
|
15
15
|
.github/workflows/ci.yml
|
|
16
16
|
.github/workflows/daily-tests.yml
|
|
17
|
+
.github/workflows/release.yml
|
|
17
18
|
docs/api-schemas/v0.yml
|
|
18
19
|
docs/api-schemas/v1.yml
|
|
19
20
|
media/logo-orange-512h.webp
|
|
@@ -63,6 +64,7 @@ tests/e2e/test_path.py
|
|
|
63
64
|
tests/e2e/test_path_builder.py
|
|
64
65
|
tests/e2e/test_path_builder_rectangle.py
|
|
65
66
|
tests/e2e/test_path_comprehensive.py
|
|
67
|
+
tests/e2e/test_path_group.py
|
|
66
68
|
tests/e2e/test_pdfdancer.py
|
|
67
69
|
tests/e2e/test_positioning.py
|
|
68
70
|
tests/e2e/test_rectangle_builder.py
|
|
@@ -70,6 +72,7 @@ tests/e2e/test_redact.py
|
|
|
70
72
|
tests/e2e/test_singular_selection.py
|
|
71
73
|
tests/e2e/test_snapshot.py
|
|
72
74
|
tests/e2e/test_template_replace.py
|
|
75
|
+
tests/e2e/test_template_replace_linebreak.py
|
|
73
76
|
tests/e2e/test_text_line_edit.py
|
|
74
77
|
tests/fixtures/DancingScript-Regular.ttf
|
|
75
78
|
tests/fixtures/Empty.pdf
|