pdfdancer-client-python 0.2.24__tar.gz → 0.2.25__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 (74) hide show
  1. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/.github/workflows/ci.yml +1 -0
  2. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/.github/workflows/daily-tests.yml +2 -1
  3. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/PKG-INFO +7 -6
  4. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/README.md +6 -5
  5. pdfdancer_client_python-0.2.25/media/logo-orange-512h.webp +0 -0
  6. pdfdancer_client_python-0.2.25/media/logo-orange-60h.webp +0 -0
  7. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/pyproject.toml +1 -1
  8. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer_client_python.egg-info/PKG-INFO +7 -6
  9. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer_client_python.egg-info/SOURCES.txt +2 -0
  10. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/pdf_assertions.py +1 -1
  11. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_context_manager.py +1 -1
  12. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_page.py +1 -1
  13. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_paragraph.py +9 -9
  14. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_singular_selection.py +2 -2
  15. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_snapshot.py +1 -1
  16. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/.claude/commands/discuss.md +0 -0
  17. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/.flake8 +0 -0
  18. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/.gitignore +0 -0
  19. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/CLAUDE.md +0 -0
  20. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/LICENSE +0 -0
  21. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/NOTICE +0 -0
  22. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/TODO.md +0 -0
  23. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/check.py +0 -0
  24. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/docs/openapi.yml +0 -0
  25. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/release.py +0 -0
  26. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/setup.cfg +0 -0
  27. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer/__init__.py +0 -0
  28. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer/exceptions.py +0 -0
  29. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer/fingerprint.py +0 -0
  30. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer/image_builder.py +0 -0
  31. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer/models.py +0 -0
  32. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer/page_builder.py +0 -0
  33. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer/paragraph_builder.py +0 -0
  34. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer/path_builder.py +0 -0
  35. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer/pdfdancer_v1.py +0 -0
  36. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer/types.py +0 -0
  37. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
  38. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
  39. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
  40. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/test.sh +0 -0
  41. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/__init__.py +0 -0
  42. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/conftest.py +0 -0
  43. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/__init__.py +0 -0
  44. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_acroform.py +0 -0
  45. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_bezier_builder.py +0 -0
  46. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_form_x_objects.py +0 -0
  47. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_image.py +0 -0
  48. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_line.py +0 -0
  49. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_line_builder.py +0 -0
  50. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_new_pdf.py +0 -0
  51. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_path.py +0 -0
  52. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_path_builder.py +0 -0
  53. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_path_builder_rectangle.py +0 -0
  54. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_path_comprehensive.py +0 -0
  55. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_pdfdancer.py +0 -0
  56. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_positioning.py +0 -0
  57. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/e2e/test_rectangle_builder.py +0 -0
  58. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/fixtures/DancingScript-Regular.ttf +0 -0
  59. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/fixtures/Empty.pdf +0 -0
  60. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
  61. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/fixtures/Showcase.pdf +0 -0
  62. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/fixtures/basic-paths.pdf +0 -0
  63. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/fixtures/form-xobject-example.pdf +0 -0
  64. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/fixtures/logo-80.png +0 -0
  65. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/fixtures/mixed-form-types.pdf +0 -0
  66. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/test_anonymous_token.py +0 -0
  67. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/test_fingerprint.py +0 -0
  68. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/test_models.py +0 -0
  69. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/test_openapi_compliance.py +0 -0
  70. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/test_path_models.py +0 -0
  71. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/test_pdf_object_equality.py +0 -0
  72. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/test_rate_limit.py +0 -0
  73. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/tests/test_standard_fonts.py +0 -0
  74. {pdfdancer_client_python-0.2.24 → pdfdancer_client_python-0.2.25}/update-api-spec.sh +0 -0
@@ -14,6 +14,7 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
  strategy:
16
16
  fail-fast: false
17
+ max-parallel: 4
17
18
  matrix:
18
19
  python-version: [ '3.12' ]
19
20
 
@@ -3,7 +3,7 @@ name: Daily Tests
3
3
  on:
4
4
  schedule:
5
5
  # Run daily at 9:00 PM UTC
6
- - cron: '0 21 * * *'
6
+ - cron: '40 10 * * *'
7
7
  workflow_dispatch: # Allow manual triggering
8
8
 
9
9
  jobs:
@@ -11,6 +11,7 @@ jobs:
11
11
  runs-on: ${{ matrix.os }}
12
12
  strategy:
13
13
  fail-fast: false
14
+ max-parallel: 4
14
15
  matrix:
15
16
  os: [ ubuntu-latest, windows-latest ]
16
17
  python-version: [ '3.10', '3.11', '3.12', '3.13' ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfdancer-client-python
3
- Version: 0.2.24
3
+ Version: 0.2.25
4
4
  Summary: Python client for PDFDancer API
5
5
  Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
6
6
  License:
@@ -239,11 +239,12 @@ Dynamic: license-file
239
239
 
240
240
  # PDFDancer Python Client
241
241
 
242
- **Getting Started with PDFDancer**
242
+ ![PDFDancer logo](media/logo-orange-60h.webp)
243
243
 
244
- PDFDancer gives you pixel-perfect programmatic control over any PDF document from Python. Locate existing elements by
245
- coordinates or text, adjust them precisely, add brand-new content, and ship the modified PDF in memory or on disk. The
246
- same API is also available for TypeScript and Java, so teams can orchestrate identical PDF workflows across stacks.
244
+ **Stop fighting PDFs. Start editing them.**
245
+
246
+ Edit text in real-world PDFs—even ones you didn't create. Move images, reposition headers, and change fonts with
247
+ pixel-perfect control from Python. The same API is also available for TypeScript and Java.
247
248
 
248
249
  > Need the raw API schema? The latest OpenAPI description lives in `docs/openapi.yml` and is published at
249
250
  > https://bucket.pdfdancer.com/api-doc/development-0.0.yml.
@@ -258,7 +259,7 @@ same API is also available for TypeScript and Java, so teams can orchestrate ide
258
259
 
259
260
  ## What Makes PDFDancer Different
260
261
 
261
- - **Edit any PDF**: Work with documents from customers, governments, or vendors—not just ones you generated.
262
+ - **Edit text in real-world PDFs**: Work with documents from customers, governments, or vendors—even ones you didn't create.
262
263
  - **Pixel-perfect positioning**: Move or add elements at exact coordinates and keep the original layout intact.
263
264
  - **Surgical text replacement**: Swap or rewrite paragraphs without reflowing the rest of the page.
264
265
  - **Form manipulation**: Inspect, fill, and update AcroForm fields programmatically.
@@ -1,10 +1,11 @@
1
1
  # PDFDancer Python Client
2
2
 
3
- **Getting Started with PDFDancer**
3
+ ![PDFDancer logo](media/logo-orange-60h.webp)
4
4
 
5
- PDFDancer gives you pixel-perfect programmatic control over any PDF document from Python. Locate existing elements by
6
- coordinates or text, adjust them precisely, add brand-new content, and ship the modified PDF in memory or on disk. The
7
- same API is also available for TypeScript and Java, so teams can orchestrate identical PDF workflows across stacks.
5
+ **Stop fighting PDFs. Start editing them.**
6
+
7
+ Edit text in real-world PDFs—even ones you didn't create. Move images, reposition headers, and change fonts with
8
+ pixel-perfect control from Python. The same API is also available for TypeScript and Java.
8
9
 
9
10
  > Need the raw API schema? The latest OpenAPI description lives in `docs/openapi.yml` and is published at
10
11
  > https://bucket.pdfdancer.com/api-doc/development-0.0.yml.
@@ -19,7 +20,7 @@ same API is also available for TypeScript and Java, so teams can orchestrate ide
19
20
 
20
21
  ## What Makes PDFDancer Different
21
22
 
22
- - **Edit any PDF**: Work with documents from customers, governments, or vendors—not just ones you generated.
23
+ - **Edit text in real-world PDFs**: Work with documents from customers, governments, or vendors—even ones you didn't create.
23
24
  - **Pixel-perfect positioning**: Move or add elements at exact coordinates and keep the original layout intact.
24
25
  - **Surgical text replacement**: Swap or rewrite paragraphs without reflowing the rest of the page.
25
26
  - **Form manipulation**: Inspect, fill, and update AcroForm fields programmatically.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pdfdancer-client-python"
7
- version = "0.2.24"
7
+ version = "0.2.25"
8
8
  description = "Python client for PDFDancer API"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfdancer-client-python
3
- Version: 0.2.24
3
+ Version: 0.2.25
4
4
  Summary: Python client for PDFDancer API
5
5
  Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
6
6
  License:
@@ -239,11 +239,12 @@ Dynamic: license-file
239
239
 
240
240
  # PDFDancer Python Client
241
241
 
242
- **Getting Started with PDFDancer**
242
+ ![PDFDancer logo](media/logo-orange-60h.webp)
243
243
 
244
- PDFDancer gives you pixel-perfect programmatic control over any PDF document from Python. Locate existing elements by
245
- coordinates or text, adjust them precisely, add brand-new content, and ship the modified PDF in memory or on disk. The
246
- same API is also available for TypeScript and Java, so teams can orchestrate identical PDF workflows across stacks.
244
+ **Stop fighting PDFs. Start editing them.**
245
+
246
+ Edit text in real-world PDFs—even ones you didn't create. Move images, reposition headers, and change fonts with
247
+ pixel-perfect control from Python. The same API is also available for TypeScript and Java.
247
248
 
248
249
  > Need the raw API schema? The latest OpenAPI description lives in `docs/openapi.yml` and is published at
249
250
  > https://bucket.pdfdancer.com/api-doc/development-0.0.yml.
@@ -258,7 +259,7 @@ same API is also available for TypeScript and Java, so teams can orchestrate ide
258
259
 
259
260
  ## What Makes PDFDancer Different
260
261
 
261
- - **Edit any PDF**: Work with documents from customers, governments, or vendors—not just ones you generated.
262
+ - **Edit text in real-world PDFs**: Work with documents from customers, governments, or vendors—even ones you didn't create.
262
263
  - **Pixel-perfect positioning**: Move or add elements at exact coordinates and keep the original layout intact.
263
264
  - **Surgical text replacement**: Swap or rewrite paragraphs without reflowing the rest of the page.
264
265
  - **Form manipulation**: Inspect, fill, and update AcroForm fields programmatically.
@@ -14,6 +14,8 @@ update-api-spec.sh
14
14
  .github/workflows/ci.yml
15
15
  .github/workflows/daily-tests.yml
16
16
  docs/openapi.yml
17
+ media/logo-orange-512h.webp
18
+ media/logo-orange-60h.webp
17
19
  src/pdfdancer/__init__.py
18
20
  src/pdfdancer/exceptions.py
19
21
  src/pdfdancer/fingerprint.py
@@ -43,7 +43,7 @@ class PDFAssertions(object):
43
43
 
44
44
  return self
45
45
 
46
- def assert_paragraph_is_at(self, text, x, y, page=0, epsilon=1e-6):
46
+ def assert_paragraph_is_at(self, text, x, y, page=0, epsilon=2): # adjust for baseline vs bounding box differences
47
47
  paragraphs = self.pdf.page(page).select_paragraphs_matching(f".*{text}.*")
48
48
  assert len(paragraphs) == 1, f"Expected 1 paragraph but got {len(paragraphs)}"
49
49
  reference = paragraphs[0].object_ref()
@@ -12,7 +12,7 @@ def test_context_manager_basic_usage():
12
12
 
13
13
  with PDFDancer.open(pdf_path, token=token, base_url=base_url) as pdf:
14
14
  paragraphs = pdf.select_paragraphs()
15
- assert len(paragraphs) == 24
15
+ assert len(paragraphs) == 20
16
16
 
17
17
 
18
18
  def test_context_manager_edit_text_only():
@@ -7,7 +7,7 @@ def test_get_all_elements():
7
7
  base_url, token, pdf_path = _require_env_and_fixture("Showcase.pdf")
8
8
 
9
9
  with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
10
- expected_total = 99
10
+ expected_total = 95
11
11
  assert (
12
12
  len(pdf.select_elements()) == expected_total
13
13
  ), f"{len(pdf.select_elements())} elements found but {expected_total} elements expected"
@@ -1,7 +1,7 @@
1
1
  import pytest
2
-
3
2
  from pdfdancer import Color, FontType, StandardFonts
4
3
  from pdfdancer.pdfdancer_v1 import PDFDancer
4
+
5
5
  from tests.e2e import _require_env_and_fixture
6
6
  from tests.e2e.pdf_assertions import PDFAssertions
7
7
 
@@ -11,19 +11,19 @@ def test_find_paragraphs_by_position():
11
11
 
12
12
  with PDFDancer.open(pdf_path, token=token, base_url=base_url, timeout=30.0) as pdf:
13
13
  paras = pdf.select_paragraphs()
14
- assert len(paras) == 24
14
+ assert len(paras) == 20
15
15
 
16
16
  paras_page0 = pdf.page(0).select_paragraphs()
17
- assert len(paras_page0) == 4
17
+ assert len(paras_page0) == 3
18
18
 
19
19
  first = paras_page0[0]
20
- assert first.internal_id == "PARAGRAPH_000005"
20
+ assert first.internal_id == "PARAGRAPH_000004"
21
21
  assert first.position is not None
22
22
  assert pytest.approx(first.position.x(), rel=0, abs=1) == 180
23
- assert pytest.approx(first.position.y(), rel=0, abs=1) == 755.2
23
+ assert pytest.approx(first.position.y(), rel=0, abs=1) == 749 # adjusted for baseline/bounding box
24
24
 
25
25
  last = paras_page0[-1]
26
- assert last.internal_id == "PARAGRAPH_000008"
26
+ assert last.internal_id == "PARAGRAPH_000006"
27
27
  assert last.position is not None
28
28
  assert pytest.approx(last.position.x(), rel=0, abs=1) == 69.3
29
29
  assert pytest.approx(last.position.y(), rel=0, abs=2) == 46.7
@@ -43,9 +43,9 @@ def test_find_paragraphs_by_text():
43
43
  )
44
44
  assert len(paras) == 1
45
45
  p = paras[0]
46
- assert p.internal_id == "PARAGRAPH_000006"
46
+ assert p.internal_id == "PARAGRAPH_000005"
47
47
  assert pytest.approx(p.position.x(), rel=0, abs=1) == 64.7
48
- assert pytest.approx(p.position.y(), rel=0, abs=2) == 661.2
48
+ assert pytest.approx(p.position.y(), rel=0, abs=2) == 642 # adjust for baseline/bounding box
49
49
 
50
50
 
51
51
  def test_select_paragraphs_matching_document_level():
@@ -114,7 +114,7 @@ def test_select_paragraphs_matching_multiple_pages():
114
114
  base_url, token, _ = _require_env_and_fixture("Showcase.pdf")
115
115
 
116
116
  with PDFDancer.new(
117
- token=token, base_url=base_url, timeout=30.0, initial_page_count=3
117
+ token=token, base_url=base_url, timeout=30.0, initial_page_count=3
118
118
  ) as pdf:
119
119
  # Add paragraphs to different pages
120
120
  pdf.new_paragraph().text("Chapter 1: Introduction").font(
@@ -51,9 +51,9 @@ def test_select_paragraph_starting_with():
51
51
  "This is regular Sans text showing alignment and styles."
52
52
  )
53
53
  assert paragraph is not None
54
- assert paragraph.internal_id == "PARAGRAPH_000006"
54
+ assert paragraph.internal_id == "PARAGRAPH_000005"
55
55
  assert pytest.approx(paragraph.position.x(), rel=0, abs=1) == 64.7
56
- assert pytest.approx(paragraph.position.y(), rel=0, abs=2) == 661.2
56
+ assert pytest.approx(paragraph.position.y(), rel=0, abs=2) == 642 # adjust for baseline/bounding box
57
57
 
58
58
 
59
59
  def test_select_paragraph_starting_with_no_match():
@@ -239,7 +239,7 @@ def test_total_element_count_matches_expected():
239
239
  with PDFDancer.open(pdf_path, token=token, base_url=base_url) as pdf:
240
240
  # Showcase.pdf - Python API filters certain types (638)
241
241
  all_elements = pdf.select_elements()
242
- assert len(all_elements) == 99, "Showcase.pdf should have 99 total elements"
242
+ assert len(all_elements) == 95, "Showcase.pdf should have 95 total elements"
243
243
 
244
244
  doc_snapshot = pdf.get_document_snapshot()
245
245
  snapshot_total = sum(len(p.elements) for p in doc_snapshot.pages)