pdfdancer-client-python 0.2.21__tar.gz → 0.2.23__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 (76) hide show
  1. pdfdancer_client_python-0.2.23/.flake8 +4 -0
  2. pdfdancer_client_python-0.2.23/.github/workflows/ci.yml +129 -0
  3. pdfdancer_client_python-0.2.23/.github/workflows/daily-tests.yml +116 -0
  4. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/.gitignore +8 -0
  5. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/PKG-INFO +1 -1
  6. pdfdancer_client_python-0.2.23/check.py +4 -0
  7. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/pyproject.toml +8 -1
  8. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer/__init__.py +36 -9
  9. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer/exceptions.py +25 -1
  10. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer/fingerprint.py +10 -10
  11. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer/image_builder.py +13 -7
  12. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer/models.py +159 -81
  13. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer/page_builder.py +23 -16
  14. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer/paragraph_builder.py +100 -48
  15. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer/path_builder.py +82 -67
  16. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer/pdfdancer_v1.py +1382 -494
  17. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer/types.py +116 -65
  18. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer_client_python.egg-info/PKG-INFO +1 -1
  19. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer_client_python.egg-info/SOURCES.txt +5 -0
  20. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/test.sh +230 -90
  21. pdfdancer_client_python-0.2.23/tests/__init__.py +1 -0
  22. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/conftest.py +1 -1
  23. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/__init__.py +14 -10
  24. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/pdf_assertions.py +277 -133
  25. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_acroform.py +1 -3
  26. pdfdancer_client_python-0.2.23/tests/e2e/test_bezier_builder.py +283 -0
  27. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_context_manager.py +132 -76
  28. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_form_x_objects.py +1 -4
  29. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_image.py +4 -18
  30. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_line.py +37 -18
  31. pdfdancer_client_python-0.2.23/tests/e2e/test_line_builder.py +255 -0
  32. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_new_pdf.py +31 -31
  33. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_page.py +15 -35
  34. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_paragraph.py +176 -118
  35. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_path.py +2 -9
  36. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_path_builder.py +118 -164
  37. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_path_builder_rectangle.py +123 -176
  38. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_path_comprehensive.py +77 -84
  39. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_pdfdancer.py +5 -3
  40. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_rectangle_builder.py +106 -161
  41. pdfdancer_client_python-0.2.23/tests/e2e/test_singular_selection.py +327 -0
  42. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_snapshot.py +83 -47
  43. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/test_anonymous_token.py +71 -61
  44. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/test_fingerprint.py +25 -19
  45. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/test_models.py +20 -5
  46. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/test_openapi_compliance.py +29 -8
  47. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/test_path_models.py +18 -37
  48. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/test_pdf_object_equality.py +70 -23
  49. pdfdancer_client_python-0.2.23/tests/test_rate_limit.py +82 -0
  50. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/test_standard_fonts.py +14 -4
  51. pdfdancer_client_python-0.2.21/.github/workflows/ci.yml +0 -41
  52. pdfdancer_client_python-0.2.21/tests/__init__.py +0 -1
  53. pdfdancer_client_python-0.2.21/tests/e2e/test_bezier_builder.py +0 -251
  54. pdfdancer_client_python-0.2.21/tests/e2e/test_line_builder.py +0 -255
  55. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/.claude/commands/discuss.md +0 -0
  56. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/CLAUDE.md +0 -0
  57. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/LICENSE +0 -0
  58. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/NOTICE +0 -0
  59. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/README.md +0 -0
  60. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/TODO.md +0 -0
  61. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/docs/openapi.yml +0 -0
  62. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/release.py +0 -0
  63. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/setup.cfg +0 -0
  64. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer_client_python.egg-info/dependency_links.txt +0 -0
  65. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer_client_python.egg-info/requires.txt +0 -0
  66. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/src/pdfdancer_client_python.egg-info/top_level.txt +0 -0
  67. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/e2e/test_positioning.py +0 -0
  68. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/fixtures/DancingScript-Regular.ttf +0 -0
  69. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/fixtures/Empty.pdf +0 -0
  70. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/fixtures/JetBrainsMono-Regular.ttf +0 -0
  71. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/fixtures/Showcase.pdf +0 -0
  72. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/fixtures/basic-paths.pdf +0 -0
  73. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/fixtures/form-xobject-example.pdf +0 -0
  74. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/fixtures/logo-80.png +0 -0
  75. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/tests/fixtures/mixed-form-types.pdf +0 -0
  76. {pdfdancer_client_python-0.2.21 → pdfdancer_client_python-0.2.23}/update-api-spec.sh +0 -0
@@ -0,0 +1,4 @@
1
+ [flake8]
2
+ max-line-length = 88
3
+ extend-ignore = E203, W503, E501
4
+ exclude = venv, build, dist, .git, __pycache__, *.egg-info
@@ -0,0 +1,129 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, staging, develop, development, dev ]
6
+ pull_request:
7
+ branches: [ main, staging, develop, development, dev ]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ test:
12
+ # Quick minimal test on non-main branches
13
+ if: github.ref != 'refs/heads/main'
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ python-version: [ '3.12' ]
19
+
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+
23
+ - name: Set up Python ${{ matrix.python-version }}
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+
28
+ - name: Create virtual environment
29
+ run: python -m venv venv
30
+
31
+ - name: Install dependencies
32
+ run: |
33
+ venv/bin/pip install --upgrade pip
34
+ venv/bin/pip install -e ".[dev]"
35
+
36
+ - name: Run linter
37
+ run: |
38
+ venv/bin/python -m flake8 src/
39
+
40
+ - name: Run tests
41
+ run: |
42
+ PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com \
43
+ PDFDANCER_TOKEN=42 \
44
+ venv/bin/python -m pytest tests/ -v --maxfail=3
45
+
46
+ - name: Build distribution packages
47
+ run: venv/bin/python -m build
48
+
49
+ - name: Validate packages
50
+ run: venv/bin/python -m twine check dist/*
51
+
52
+
53
+ test-full-matrix:
54
+ # Full matrix only on main
55
+ if: github.ref == 'refs/heads/main'
56
+ runs-on: ${{ matrix.os }}
57
+ strategy:
58
+ fail-fast: false
59
+ matrix:
60
+ os: [ ubuntu-latest, windows-latest ]
61
+ python-version: [ '3.10', '3.11', '3.12', '3.13' ]
62
+
63
+ steps:
64
+ - uses: actions/checkout@v4
65
+
66
+ - name: Set up Python ${{ matrix.python-version }}
67
+ uses: actions/setup-python@v5
68
+ with:
69
+ python-version: ${{ matrix.python-version }}
70
+
71
+ - name: Create virtual environment (Unix)
72
+ if: runner.os != 'Windows'
73
+ run: python -m venv venv
74
+
75
+ - name: Install dependencies (Unix)
76
+ if: runner.os != 'Windows'
77
+ run: |
78
+ venv/bin/pip install --upgrade pip
79
+ venv/bin/pip install -e ".[dev]"
80
+
81
+ - name: Run linter (Unix)
82
+ if: runner.os != 'Windows'
83
+ run: |
84
+ venv/bin/python -m flake8 src/
85
+
86
+ - name: Run tests (Unix)
87
+ if: runner.os != 'Windows'
88
+ run: |
89
+ PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com \
90
+ PDFDANCER_TOKEN=42 \
91
+ venv/bin/python -m pytest tests/ -v --maxfail=3
92
+
93
+ - name: Build & Validate (Unix)
94
+ if: runner.os != 'Windows'
95
+ run: |
96
+ venv/bin/python -m build
97
+ venv/bin/python -m twine check dist/*
98
+
99
+ - name: Create virtual environment (Windows)
100
+ if: runner.os == 'Windows'
101
+ shell: cmd
102
+ run: python -m venv venv
103
+
104
+ - name: Install dependencies (Windows)
105
+ if: runner.os == 'Windows'
106
+ shell: cmd
107
+ run: |
108
+ venv\Scripts\pip install --upgrade pip
109
+ venv\Scripts\pip install -e ".[dev]"
110
+
111
+ - name: Run linter (Windows)
112
+ if: runner.os == 'Windows'
113
+ run: |
114
+ venv\Scripts\python -m flake8 src/
115
+
116
+ - name: Run tests (Windows)
117
+ if: runner.os == 'Windows'
118
+ shell: cmd
119
+ run: |
120
+ set PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com
121
+ set PDFDANCER_TOKEN=42
122
+ venv\Scripts\python -m pytest tests/ -v --maxfail=3
123
+
124
+ - name: Build & Validate (Windows)
125
+ if: runner.os == 'Windows'
126
+ shell: cmd
127
+ run: |
128
+ venv\Scripts\python -m build
129
+ venv\Scripts\python -m twine check dist/*
@@ -0,0 +1,116 @@
1
+ name: Daily Tests
2
+
3
+ on:
4
+ schedule:
5
+ # Run daily at 9:00 PM UTC
6
+ - cron: '0 21 * * *'
7
+ workflow_dispatch: # Allow manual triggering
8
+
9
+ jobs:
10
+ daily-test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ ubuntu-latest, windows-latest ]
16
+ python-version: [ '3.10', '3.11', '3.12', '3.13' ]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+
26
+ - name: Create virtual environment (Unix)
27
+ if: runner.os != 'Windows'
28
+ run: python -m venv venv
29
+
30
+ - name: Install dependencies (Unix)
31
+ if: runner.os != 'Windows'
32
+ run: |
33
+ venv/bin/pip install --upgrade pip
34
+ venv/bin/pip install -e ".[dev]"
35
+
36
+ - name: Run linter (Unix)
37
+ if: runner.os != 'Windows'
38
+ run: |
39
+ venv/bin/python -m flake8 src/
40
+
41
+ - name: Run tests (Unix)
42
+ if: runner.os != 'Windows'
43
+ run: |
44
+ PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com \
45
+ PDFDANCER_TOKEN=42 \
46
+ venv/bin/python -m pytest tests/ -v --maxfail=3
47
+
48
+ - name: Build & Validate (Unix)
49
+ if: runner.os != 'Windows'
50
+ run: |
51
+ venv/bin/python -m build
52
+ venv/bin/python -m twine check dist/*
53
+
54
+ - name: Create virtual environment (Windows)
55
+ if: runner.os == 'Windows'
56
+ shell: cmd
57
+ run: python -m venv venv
58
+
59
+ - name: Install dependencies (Windows)
60
+ if: runner.os == 'Windows'
61
+ shell: cmd
62
+ run: |
63
+ venv\Scripts\pip install --upgrade pip
64
+ venv\Scripts\pip install -e ".[dev]"
65
+
66
+ - name: Run linter (Windows)
67
+ if: runner.os == 'Windows'
68
+ run: |
69
+ venv\Scripts\python -m flake8 src/
70
+
71
+ - name: Run tests (Windows)
72
+ if: runner.os == 'Windows'
73
+ shell: cmd
74
+ run: |
75
+ set PDFDANCER_BASE_URL=https://api-staging.pdfdancer.com
76
+ set PDFDANCER_TOKEN=42
77
+ venv\Scripts\python -m pytest tests/ -v --maxfail=3
78
+
79
+ - name: Build & Validate (Windows)
80
+ if: runner.os == 'Windows'
81
+ shell: cmd
82
+ run: |
83
+ venv\Scripts\python -m build
84
+ venv\Scripts\python -m twine check dist/*
85
+
86
+ notify-on-failure:
87
+ needs: daily-test
88
+ runs-on: ubuntu-latest
89
+ if: failure()
90
+ steps:
91
+ - name: Create issue on failure
92
+ uses: actions/github-script@v7
93
+ with:
94
+ script: |
95
+ const title = `Daily Tests Failed - ${new Date().toISOString().split('T')[0]}`;
96
+ const body = `The daily test run has failed. Please check the workflow run for details.\n\n[Workflow Run](${context.payload.repository.html_url}/actions/runs/${context.runId})`;
97
+
98
+ // Check if an issue already exists
99
+ const issues = await github.rest.issues.listForRepo({
100
+ owner: context.repo.owner,
101
+ repo: context.repo.repo,
102
+ state: 'open',
103
+ labels: 'daily-test-failure'
104
+ });
105
+
106
+ const existingIssue = issues.data.find(issue => issue.title === title);
107
+
108
+ if (!existingIssue) {
109
+ await github.rest.issues.create({
110
+ owner: context.repo.owner,
111
+ repo: context.repo.repo,
112
+ title: title,
113
+ body: body,
114
+ labels: ['daily-test-failure', 'automated']
115
+ });
116
+ }
@@ -9,3 +9,11 @@ src/pdfdancer_client_python.egg-info
9
9
  /logs/
10
10
  /venv
11
11
  /venv-*
12
+
13
+ # Python cache
14
+ __pycache__/
15
+ *.py[cod]
16
+ *$py.class
17
+ *.so
18
+ .Python
19
+ /.run/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdfdancer-client-python
3
- Version: 0.2.21
3
+ Version: 0.2.23
4
4
  Summary: Python client for PDFDancer API
5
5
  Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
6
6
  License:
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+
3
+
4
+ isort src/ tests/ && black src/ tests/ && flake8 src/
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pdfdancer-client-python"
7
- version = "0.2.21"
7
+ version = "0.2.23"
8
8
  description = "Python client for PDFDancer API"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -48,6 +48,9 @@ Repository = "https://github.com/MenschMachine/pdfdancer-client-python"
48
48
  [tool.setuptools.packages.find]
49
49
  where = ["src"]
50
50
 
51
+ [tool.isort]
52
+ profile = "black"
53
+
51
54
  [tool.black]
52
55
  line-length = 88
53
56
  target-version = ['py310']
@@ -55,3 +58,7 @@ target-version = ['py310']
55
58
  [tool.mypy]
56
59
  python_version = "3.10"
57
60
  strict = true
61
+
62
+ [tool.poetry.scripts]
63
+ lint = "bash -c 'flake8 src && mypy src'"
64
+ format = "bash -c 'black src && isort src'"
@@ -7,17 +7,42 @@ mirrors the Java client structure and functionality.
7
7
  """
8
8
 
9
9
  from .exceptions import (
10
- PdfDancerException, FontNotFoundException, ValidationException,
11
- HttpClientException, SessionException
10
+ FontNotFoundException,
11
+ HttpClientException,
12
+ PdfDancerException,
13
+ RateLimitException,
14
+ SessionException,
15
+ ValidationException,
12
16
  )
13
17
  from .models import (
14
- ObjectRef, Position, ObjectType, Font, Color, Image, BoundingRect, Paragraph, FormFieldRef, TextObjectRef,
15
- PageRef, PositionMode, ShapeType, Point, StandardFonts, PageSize, Orientation, TextStatus, FontRecommendation,
16
- FontType, PathSegment, Line, Bezier, Path
18
+ Bezier,
19
+ BoundingRect,
20
+ Color,
21
+ Font,
22
+ FontRecommendation,
23
+ FontType,
24
+ FormFieldRef,
25
+ Image,
26
+ Line,
27
+ ObjectRef,
28
+ ObjectType,
29
+ Orientation,
30
+ PageRef,
31
+ PageSize,
32
+ Paragraph,
33
+ Path,
34
+ PathSegment,
35
+ Point,
36
+ Position,
37
+ PositionMode,
38
+ ShapeType,
39
+ StandardFonts,
40
+ TextObjectRef,
41
+ TextStatus,
17
42
  )
18
- from .paragraph_builder import ParagraphBuilder
19
43
  from .page_builder import PageBuilder
20
- from .path_builder import PathBuilder, LineBuilder, BezierBuilder
44
+ from .paragraph_builder import ParagraphBuilder
45
+ from .path_builder import BezierBuilder, LineBuilder, PathBuilder
21
46
 
22
47
  __version__ = "1.0.0"
23
48
  __all__ = [
@@ -56,11 +81,13 @@ __all__ = [
56
81
  "ValidationException",
57
82
  "HttpClientException",
58
83
  "SessionException",
59
- "set_ssl_verify"
84
+ "RateLimitException",
85
+ "set_ssl_verify",
60
86
  ]
61
87
 
62
- from .pdfdancer_v1 import PDFDancer
63
88
  from . import pdfdancer_v1
89
+ from .pdfdancer_v1 import PDFDancer
90
+
64
91
 
65
92
  def set_ssl_verify(enabled: bool) -> None:
66
93
  """
@@ -35,7 +35,12 @@ class HttpClientException(PdfDancerException):
35
35
  Wraps httpx exceptions and HTTP errors from the API.
36
36
  """
37
37
 
38
- def __init__(self, message: str, response: Optional[httpx.Response] = None, cause: Optional[Exception] = None):
38
+ def __init__(
39
+ self,
40
+ message: str,
41
+ response: Optional[httpx.Response] = None,
42
+ cause: Optional[Exception] = None,
43
+ ):
39
44
  super().__init__(message, cause)
40
45
  self.response = response
41
46
  self.status_code = response.status_code if response else None
@@ -46,6 +51,7 @@ class SessionException(PdfDancerException):
46
51
  Exception raised for session-related errors.
47
52
  Occurs when session creation fails or session is invalid.
48
53
  """
54
+
49
55
  pass
50
56
 
51
57
 
@@ -54,4 +60,22 @@ class ValidationException(PdfDancerException):
54
60
  Exception raised for input validation errors.
55
61
  Equivalent to IllegalArgumentException in the Java client.
56
62
  """
63
+
57
64
  pass
65
+
66
+
67
+ class RateLimitException(PdfDancerException):
68
+ """
69
+ Exception raised when the API rate limit is exceeded (HTTP 429).
70
+ Includes retry-after information if provided by the server.
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ message: str,
76
+ retry_after: Optional[int] = None,
77
+ response: Optional[httpx.Response] = None,
78
+ ):
79
+ super().__init__(message)
80
+ self.retry_after = retry_after
81
+ self.response = response
@@ -30,14 +30,14 @@ class Fingerprint:
30
30
  install_salt = cls._get_or_create_salt()
31
31
 
32
32
  fingerprint_data = (
33
- ip_hash +
34
- uid_hash +
35
- os_type +
36
- sdk_language +
37
- timezone +
38
- locale_str +
39
- domain_hash +
40
- install_salt
33
+ ip_hash
34
+ + uid_hash
35
+ + os_type
36
+ + sdk_language
37
+ + timezone
38
+ + locale_str
39
+ + domain_hash
40
+ + install_salt
41
41
  )
42
42
 
43
43
  return cls._hash(fingerprint_data)
@@ -63,7 +63,7 @@ class Fingerprint:
63
63
  """Get timezone name."""
64
64
  try:
65
65
  tz = datetime.now().astimezone().tzinfo
66
- timezone_name = getattr(tz, 'key', str(tz))
66
+ timezone_name = getattr(tz, "key", str(tz))
67
67
  return timezone_name
68
68
  except Exception:
69
69
  return "unknown"
@@ -118,4 +118,4 @@ class Fingerprint:
118
118
  Returns:
119
119
  Hexadecimal SHA256 hash
120
120
  """
121
- return hashlib.sha256(value.encode('utf-8')).hexdigest()
121
+ return hashlib.sha256(value.encode("utf-8")).hexdigest()
@@ -1,11 +1,17 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING
5
+
6
+ from pdfdancer import Image, Position, ValidationException
2
7
 
3
- from pdfdancer import ValidationException, Image, Position
8
+ if TYPE_CHECKING:
9
+ from .pdfdancer_v1 import PDFDancer
4
10
 
5
11
 
6
12
  class ImageBuilder:
7
13
 
8
- def __init__(self, client: 'PDFDancer'):
14
+ def __init__(self, client: "PDFDancer"):
9
15
  """
10
16
  Initialize the image builder with a client reference.
11
17
 
@@ -18,11 +24,11 @@ class ImageBuilder:
18
24
  self._client = client
19
25
  self._image = Image()
20
26
 
21
- def from_file(self, img_path: Path) -> 'ImageBuilder':
27
+ def from_file(self, img_path: Path) -> "ImageBuilder":
22
28
  self._image.data = img_path.read_bytes()
23
29
  return self
24
30
 
25
- def at(self, page, x, y) -> 'ImageBuilder':
31
+ def at(self, page, x, y) -> "ImageBuilder":
26
32
  self._image.position = Position.at_page_coordinates(page, x, y)
27
33
  return self
28
34
 
@@ -33,7 +39,7 @@ class ImageBuilder:
33
39
 
34
40
  class ImageOnPageBuilder:
35
41
 
36
- def __init__(self, client: 'PDFDancer', page_index: int):
42
+ def __init__(self, client: "PDFDancer", page_index: int):
37
43
  """
38
44
  Initialize the image builder with a client reference.
39
45
 
@@ -47,11 +53,11 @@ class ImageOnPageBuilder:
47
53
  self._image = Image()
48
54
  self._page_index = page_index
49
55
 
50
- def from_file(self, img_path: Path) -> 'ImageOnPageBuilder':
56
+ def from_file(self, img_path: Path) -> "ImageOnPageBuilder":
51
57
  self._image.data = img_path.read_bytes()
52
58
  return self
53
59
 
54
- def at(self, x, y) -> 'ImageOnPageBuilder':
60
+ def at(self, x, y) -> "ImageOnPageBuilder":
55
61
  self._image.position = Position.at_page_coordinates(self._page_index, x, y)
56
62
  return self
57
63