kovapdf 0.1.0__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.
- kovapdf-0.1.0/.gitignore +10 -0
- kovapdf-0.1.0/CHANGELOG.md +7 -0
- kovapdf-0.1.0/LICENSE +27 -0
- kovapdf-0.1.0/PKG-INFO +180 -0
- kovapdf-0.1.0/README.md +162 -0
- kovapdf-0.1.0/SECURITY.md +17 -0
- kovapdf-0.1.0/examples/compress.py +18 -0
- kovapdf-0.1.0/examples/merge.py +22 -0
- kovapdf-0.1.0/examples/sign_request.py +31 -0
- kovapdf-0.1.0/examples/verify_webhook.py +50 -0
- kovapdf-0.1.0/pyproject.toml +39 -0
- kovapdf-0.1.0/src/kovapdf/__init__.py +34 -0
- kovapdf-0.1.0/src/kovapdf/__main__.py +5 -0
- kovapdf-0.1.0/src/kovapdf/_generated.py +598 -0
- kovapdf-0.1.0/src/kovapdf/_http.py +209 -0
- kovapdf-0.1.0/src/kovapdf/cli.py +271 -0
- kovapdf-0.1.0/src/kovapdf/client.py +348 -0
- kovapdf-0.1.0/src/kovapdf/errors.py +71 -0
- kovapdf-0.1.0/src/kovapdf/py.typed +0 -0
- kovapdf-0.1.0/src/kovapdf/webhooks.py +68 -0
- kovapdf-0.1.0/tests/conftest.py +21 -0
- kovapdf-0.1.0/tests/mock_server.py +165 -0
- kovapdf-0.1.0/tests/test_cli.py +94 -0
- kovapdf-0.1.0/tests/test_client.py +387 -0
- kovapdf-0.1.0/tests/test_examples.py +39 -0
- kovapdf-0.1.0/tests/test_generated.py +22 -0
kovapdf-0.1.0/.gitignore
ADDED
kovapdf-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KovaPDF
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
This licence covers the source code in this package only. The KovaPDF name,
|
|
26
|
+
logos, images, fonts and website text are not covered by it and are not
|
|
27
|
+
licensed for reuse.
|
kovapdf-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: kovapdf
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The official KovaPDF client for Python: compress, merge, split, convert, read scans, redact, check signatures and send documents for signature. Includes the kovapdf command line tool.
|
|
5
|
+
Project-URL: Homepage, https://www.kovapdf.com
|
|
6
|
+
Project-URL: Documentation, https://www.kovapdf.com/developers
|
|
7
|
+
Project-URL: Security, https://www.kovapdf.com/security
|
|
8
|
+
Author-email: KovaPDF <support@kovapdf.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: bijoy,compress,e-signature,kovapdf,merge,ocr,pdf,redact,unicode
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Office/Business
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# kovapdf
|
|
20
|
+
|
|
21
|
+
The official KovaPDF client for Python, with the `kovapdf` command line tool.
|
|
22
|
+
Compress, merge, split, reorder, convert, read scans, redact and check the
|
|
23
|
+
signatures of PDF files, convert Bangla and Hindi text between Bijoy,
|
|
24
|
+
Kruti Dev and Unicode, and send documents for signature.
|
|
25
|
+
|
|
26
|
+
The work is done by KovaPDF: https://www.kovapdf.com
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install kovapdf
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Python 3.10 or newer. No dependencies: only the standard library.
|
|
35
|
+
|
|
36
|
+
## Your API key
|
|
37
|
+
|
|
38
|
+
Make a key in your KovaPDF dashboard (Developer API) and put it in the
|
|
39
|
+
environment. The client reads `KOVAPDF_API_KEY`; never write a key into code.
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
export KOVAPDF_API_KEY=kova_test_...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A test key (`kova_test_`) is free to make and stamps its PDFs TEST. A live key
|
|
46
|
+
(`kova_live_`) needs API access on your account.
|
|
47
|
+
|
|
48
|
+
## Use it
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from kovapdf import KovaPDF
|
|
52
|
+
|
|
53
|
+
kova = KovaPDF() # the key comes from KOVAPDF_API_KEY
|
|
54
|
+
|
|
55
|
+
result = kova.compress("report.pdf", level="strong", save_to="report-small.pdf")
|
|
56
|
+
print(result.saved) # ['report-small.pdf']
|
|
57
|
+
print(result.attribution) # Done with KovaPDF (kovapdf.com).
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Every tool has its own method, made from KovaPDF's tool manifest:
|
|
61
|
+
`compress`, `merge`, `split`, `organize`, `convert`, `ocr`, `redact`,
|
|
62
|
+
`bijoy_to_unicode`, `unicode_to_bijoy`, `kruti_to_unicode` and `verify_job`.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
kova.merge(["a.pdf", "b.pdf"], save_to="merged.pdf")
|
|
66
|
+
kova.split("big.pdf", mode="range", pages="1-3,5", save_to="out/")
|
|
67
|
+
print(kova.bijoy_to_unicode(text="Avwg evsjvq Mvb MvB").text)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A file can be a path, an https address, bytes, an open file, or a dict:
|
|
71
|
+
`{"file_id": ...}` from an earlier upload, `{"url": ...}`, or
|
|
72
|
+
`{"data": b"...", "name": "a.pdf"}`.
|
|
73
|
+
|
|
74
|
+
### Step by step
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
f = kova.files.upload("in.pdf") # kept for one hour
|
|
78
|
+
job = kova.jobs.create("compress", [f["id"]], {"level": "basic"})
|
|
79
|
+
done = kova.jobs.wait(job["id"], timeout=300)
|
|
80
|
+
data = kova.download(done["result"]["files"][0]["download"]["url"]) # the link works for 15 minutes
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`kova.run(tool, inputs, options)` does all of that in one call.
|
|
84
|
+
|
|
85
|
+
### Signature requests and verify
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
request = kova.sign_requests.create("agreement.pdf", {
|
|
89
|
+
"title": "Service agreement",
|
|
90
|
+
"sequential": False,
|
|
91
|
+
"signers": [{"name": "Alex Rivera", "email": "alex@example.com", "order": 1}],
|
|
92
|
+
"fields": [{"signer": 0, "kind": "SIGNATURE", "page": 1, "x": 72, "y": 640, "width": 180, "height": 48}],
|
|
93
|
+
})
|
|
94
|
+
kova.sign_requests.list()
|
|
95
|
+
kova.sign_requests.get(request["id"])
|
|
96
|
+
kova.sign_requests.remind(request["id"])
|
|
97
|
+
kova.sign_requests.cancel(request["id"])
|
|
98
|
+
pdf = kova.sign_requests.document(request["id"], version="final")
|
|
99
|
+
|
|
100
|
+
report = kova.verify("signed.pdf") # every digital signature, checked
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Webhooks
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from kovapdf import verify_webhook
|
|
107
|
+
|
|
108
|
+
# raw_body: the request body exactly as received, before JSON parsing
|
|
109
|
+
event = verify_webhook(raw_body, request.headers, os.environ["KOVAPDF_WEBHOOK_SECRET"])
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
It checks the `KovaPDF-Signature` header (`t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">`)
|
|
113
|
+
and refuses a timestamp older than five minutes.
|
|
114
|
+
|
|
115
|
+
## Command line
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
kovapdf compress in.pdf -o out.pdf --level strong
|
|
119
|
+
kovapdf merge a.pdf b.pdf -o merged.pdf
|
|
120
|
+
kovapdf verify signed.pdf
|
|
121
|
+
kovapdf bijoy-to-unicode "Avwg evsjvq Mvb MvB"
|
|
122
|
+
echo "Avwg" | kovapdf bijoy-to-unicode
|
|
123
|
+
kovapdf tools # every tool and its options
|
|
124
|
+
python -m kovapdf --help
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The command prints `Done with KovaPDF (kovapdf.com).` after each result. It
|
|
128
|
+
never overwrites a file unless you add `--force`.
|
|
129
|
+
|
|
130
|
+
## Errors
|
|
131
|
+
|
|
132
|
+
Every error is a `KovaPDFError` with a stable `code` and a `message` you can
|
|
133
|
+
show to a person. A few need care:
|
|
134
|
+
|
|
135
|
+
| code | what it means |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `integration_paused` | KovaPDF has paused connections from this kind of app for now. Nothing was changed. Try again later, or use https://www.kovapdf.com. |
|
|
138
|
+
| `tool_paused` | This tool is paused for apps right now. It still works on https://www.kovapdf.com. |
|
|
139
|
+
| `not_available` | This KovaPDF feature is not available yet. |
|
|
140
|
+
| `limit_reached`, `rate_limited`, `daily_limit` | A limit was reached; `retry_after` says when to try again. |
|
|
141
|
+
| `timeout` | The job is still running; ask for it later with `jobs.get(id)`. |
|
|
142
|
+
|
|
143
|
+
Paused answers are never retried.
|
|
144
|
+
|
|
145
|
+
## Limits, and how the client behaves
|
|
146
|
+
|
|
147
|
+
Your account's own limits apply, exactly as on the website; your dashboard
|
|
148
|
+
shows them. Files you upload and results are deleted within an hour, and a
|
|
149
|
+
result link works for 15 minutes.
|
|
150
|
+
|
|
151
|
+
The client is gentle with KovaPDF by design:
|
|
152
|
+
|
|
153
|
+
- at most 2 calls at once per client, across threads (you can set up to 4);
|
|
154
|
+
- a job is checked no more often than every 2 seconds, slowing to every 10;
|
|
155
|
+
- `Retry-After` is always honoured; otherwise it waits 2 seconds, then longer
|
|
156
|
+
(up to a minute), and tries at most 5 more times, only after a busy answer
|
|
157
|
+
or a network error;
|
|
158
|
+
- every call says who it is: `User-Agent: kovapdf-sdk-python/<version> (+https://www.kovapdf.com)`
|
|
159
|
+
and `X-KovaPDF-Client: sdk`.
|
|
160
|
+
|
|
161
|
+
## Attribution
|
|
162
|
+
|
|
163
|
+
Results carry `attribution = "Done with KovaPDF (kovapdf.com)."`. Please show
|
|
164
|
+
it to the person you did the work for, with a link to https://www.kovapdf.com.
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
`src/kovapdf/_generated.py` is made from KovaPDF's tool manifest and OpenAPI
|
|
169
|
+
file by `packages/sdk-js/scripts/generate.mjs` in the KovaPDF repository; a
|
|
170
|
+
test fails when it drifts. Run the tests with:
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
python -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
|
|
174
|
+
.venv/bin/pytest
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Licence
|
|
178
|
+
|
|
179
|
+
MIT for the code (see LICENSE). The KovaPDF name, logos and images are not
|
|
180
|
+
covered by it.
|
kovapdf-0.1.0/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# kovapdf
|
|
2
|
+
|
|
3
|
+
The official KovaPDF client for Python, with the `kovapdf` command line tool.
|
|
4
|
+
Compress, merge, split, reorder, convert, read scans, redact and check the
|
|
5
|
+
signatures of PDF files, convert Bangla and Hindi text between Bijoy,
|
|
6
|
+
Kruti Dev and Unicode, and send documents for signature.
|
|
7
|
+
|
|
8
|
+
The work is done by KovaPDF: https://www.kovapdf.com
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pip install kovapdf
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Python 3.10 or newer. No dependencies: only the standard library.
|
|
17
|
+
|
|
18
|
+
## Your API key
|
|
19
|
+
|
|
20
|
+
Make a key in your KovaPDF dashboard (Developer API) and put it in the
|
|
21
|
+
environment. The client reads `KOVAPDF_API_KEY`; never write a key into code.
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
export KOVAPDF_API_KEY=kova_test_...
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
A test key (`kova_test_`) is free to make and stamps its PDFs TEST. A live key
|
|
28
|
+
(`kova_live_`) needs API access on your account.
|
|
29
|
+
|
|
30
|
+
## Use it
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from kovapdf import KovaPDF
|
|
34
|
+
|
|
35
|
+
kova = KovaPDF() # the key comes from KOVAPDF_API_KEY
|
|
36
|
+
|
|
37
|
+
result = kova.compress("report.pdf", level="strong", save_to="report-small.pdf")
|
|
38
|
+
print(result.saved) # ['report-small.pdf']
|
|
39
|
+
print(result.attribution) # Done with KovaPDF (kovapdf.com).
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Every tool has its own method, made from KovaPDF's tool manifest:
|
|
43
|
+
`compress`, `merge`, `split`, `organize`, `convert`, `ocr`, `redact`,
|
|
44
|
+
`bijoy_to_unicode`, `unicode_to_bijoy`, `kruti_to_unicode` and `verify_job`.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
kova.merge(["a.pdf", "b.pdf"], save_to="merged.pdf")
|
|
48
|
+
kova.split("big.pdf", mode="range", pages="1-3,5", save_to="out/")
|
|
49
|
+
print(kova.bijoy_to_unicode(text="Avwg evsjvq Mvb MvB").text)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
A file can be a path, an https address, bytes, an open file, or a dict:
|
|
53
|
+
`{"file_id": ...}` from an earlier upload, `{"url": ...}`, or
|
|
54
|
+
`{"data": b"...", "name": "a.pdf"}`.
|
|
55
|
+
|
|
56
|
+
### Step by step
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
f = kova.files.upload("in.pdf") # kept for one hour
|
|
60
|
+
job = kova.jobs.create("compress", [f["id"]], {"level": "basic"})
|
|
61
|
+
done = kova.jobs.wait(job["id"], timeout=300)
|
|
62
|
+
data = kova.download(done["result"]["files"][0]["download"]["url"]) # the link works for 15 minutes
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`kova.run(tool, inputs, options)` does all of that in one call.
|
|
66
|
+
|
|
67
|
+
### Signature requests and verify
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
request = kova.sign_requests.create("agreement.pdf", {
|
|
71
|
+
"title": "Service agreement",
|
|
72
|
+
"sequential": False,
|
|
73
|
+
"signers": [{"name": "Alex Rivera", "email": "alex@example.com", "order": 1}],
|
|
74
|
+
"fields": [{"signer": 0, "kind": "SIGNATURE", "page": 1, "x": 72, "y": 640, "width": 180, "height": 48}],
|
|
75
|
+
})
|
|
76
|
+
kova.sign_requests.list()
|
|
77
|
+
kova.sign_requests.get(request["id"])
|
|
78
|
+
kova.sign_requests.remind(request["id"])
|
|
79
|
+
kova.sign_requests.cancel(request["id"])
|
|
80
|
+
pdf = kova.sign_requests.document(request["id"], version="final")
|
|
81
|
+
|
|
82
|
+
report = kova.verify("signed.pdf") # every digital signature, checked
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Webhooks
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from kovapdf import verify_webhook
|
|
89
|
+
|
|
90
|
+
# raw_body: the request body exactly as received, before JSON parsing
|
|
91
|
+
event = verify_webhook(raw_body, request.headers, os.environ["KOVAPDF_WEBHOOK_SECRET"])
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
It checks the `KovaPDF-Signature` header (`t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">`)
|
|
95
|
+
and refuses a timestamp older than five minutes.
|
|
96
|
+
|
|
97
|
+
## Command line
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
kovapdf compress in.pdf -o out.pdf --level strong
|
|
101
|
+
kovapdf merge a.pdf b.pdf -o merged.pdf
|
|
102
|
+
kovapdf verify signed.pdf
|
|
103
|
+
kovapdf bijoy-to-unicode "Avwg evsjvq Mvb MvB"
|
|
104
|
+
echo "Avwg" | kovapdf bijoy-to-unicode
|
|
105
|
+
kovapdf tools # every tool and its options
|
|
106
|
+
python -m kovapdf --help
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The command prints `Done with KovaPDF (kovapdf.com).` after each result. It
|
|
110
|
+
never overwrites a file unless you add `--force`.
|
|
111
|
+
|
|
112
|
+
## Errors
|
|
113
|
+
|
|
114
|
+
Every error is a `KovaPDFError` with a stable `code` and a `message` you can
|
|
115
|
+
show to a person. A few need care:
|
|
116
|
+
|
|
117
|
+
| code | what it means |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `integration_paused` | KovaPDF has paused connections from this kind of app for now. Nothing was changed. Try again later, or use https://www.kovapdf.com. |
|
|
120
|
+
| `tool_paused` | This tool is paused for apps right now. It still works on https://www.kovapdf.com. |
|
|
121
|
+
| `not_available` | This KovaPDF feature is not available yet. |
|
|
122
|
+
| `limit_reached`, `rate_limited`, `daily_limit` | A limit was reached; `retry_after` says when to try again. |
|
|
123
|
+
| `timeout` | The job is still running; ask for it later with `jobs.get(id)`. |
|
|
124
|
+
|
|
125
|
+
Paused answers are never retried.
|
|
126
|
+
|
|
127
|
+
## Limits, and how the client behaves
|
|
128
|
+
|
|
129
|
+
Your account's own limits apply, exactly as on the website; your dashboard
|
|
130
|
+
shows them. Files you upload and results are deleted within an hour, and a
|
|
131
|
+
result link works for 15 minutes.
|
|
132
|
+
|
|
133
|
+
The client is gentle with KovaPDF by design:
|
|
134
|
+
|
|
135
|
+
- at most 2 calls at once per client, across threads (you can set up to 4);
|
|
136
|
+
- a job is checked no more often than every 2 seconds, slowing to every 10;
|
|
137
|
+
- `Retry-After` is always honoured; otherwise it waits 2 seconds, then longer
|
|
138
|
+
(up to a minute), and tries at most 5 more times, only after a busy answer
|
|
139
|
+
or a network error;
|
|
140
|
+
- every call says who it is: `User-Agent: kovapdf-sdk-python/<version> (+https://www.kovapdf.com)`
|
|
141
|
+
and `X-KovaPDF-Client: sdk`.
|
|
142
|
+
|
|
143
|
+
## Attribution
|
|
144
|
+
|
|
145
|
+
Results carry `attribution = "Done with KovaPDF (kovapdf.com)."`. Please show
|
|
146
|
+
it to the person you did the work for, with a link to https://www.kovapdf.com.
|
|
147
|
+
|
|
148
|
+
## Development
|
|
149
|
+
|
|
150
|
+
`src/kovapdf/_generated.py` is made from KovaPDF's tool manifest and OpenAPI
|
|
151
|
+
file by `packages/sdk-js/scripts/generate.mjs` in the KovaPDF repository; a
|
|
152
|
+
test fails when it drifts. Run the tests with:
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
python -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
|
|
156
|
+
.venv/bin/pytest
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Licence
|
|
160
|
+
|
|
161
|
+
MIT for the code (see LICENSE). The KovaPDF name, logos and images are not
|
|
162
|
+
covered by it.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Please report a security problem in this package, or in KovaPDF itself, to
|
|
4
|
+
**support@kovapdf.com** (the contact in https://www.kovapdf.com/.well-known/security.txt).
|
|
5
|
+
How we handle files, encryption and reports: https://www.kovapdf.com/security
|
|
6
|
+
|
|
7
|
+
Please do not open a public issue for a security problem. Tell us what you
|
|
8
|
+
found and how to reproduce it; we answer as soon as we can.
|
|
9
|
+
|
|
10
|
+
## How this package handles your key
|
|
11
|
+
|
|
12
|
+
- The API key is read from the `api_key` argument or the `KOVAPDF_API_KEY`
|
|
13
|
+
environment variable. It is never written to disk or logged.
|
|
14
|
+
- The key is only sent over https (plain http is allowed for `localhost` only,
|
|
15
|
+
for local tests).
|
|
16
|
+
- Result download links need no key, and the key is never sent to them.
|
|
17
|
+
- There are no runtime dependencies.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Compress a PDF. KOVAPDF_API_KEY=kova_... python examples/compress.py in.pdf [out.pdf]"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from kovapdf import KovaPDF, KovaPDFError
|
|
6
|
+
|
|
7
|
+
if len(sys.argv) < 2:
|
|
8
|
+
sys.exit("Usage: python examples/compress.py in.pdf [out.pdf]")
|
|
9
|
+
source = sys.argv[1]
|
|
10
|
+
target = sys.argv[2] if len(sys.argv) > 2 else "compressed.pdf"
|
|
11
|
+
|
|
12
|
+
kova = KovaPDF() # the key comes from KOVAPDF_API_KEY
|
|
13
|
+
try:
|
|
14
|
+
result = kova.compress(source, level="strong", save_to=target)
|
|
15
|
+
except KovaPDFError as e:
|
|
16
|
+
sys.exit(f"{e.code}: {e.message}")
|
|
17
|
+
print("Saved", ", ".join(result.saved))
|
|
18
|
+
print(result.attribution) # Done with KovaPDF (kovapdf.com).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Merge PDFs in order. KOVAPDF_API_KEY=kova_... python examples/merge.py a.pdf b.pdf [...] [-o merged.pdf]"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from kovapdf import KovaPDF, KovaPDFError
|
|
6
|
+
|
|
7
|
+
args = sys.argv[1:]
|
|
8
|
+
target = "merged.pdf"
|
|
9
|
+
if "-o" in args:
|
|
10
|
+
i = args.index("-o")
|
|
11
|
+
target = args[i + 1]
|
|
12
|
+
del args[i : i + 2]
|
|
13
|
+
if len(args) < 2:
|
|
14
|
+
sys.exit("Usage: python examples/merge.py a.pdf b.pdf [...] [-o merged.pdf]")
|
|
15
|
+
|
|
16
|
+
kova = KovaPDF()
|
|
17
|
+
try:
|
|
18
|
+
result = kova.merge(args, save_to=target, timeout=300)
|
|
19
|
+
except KovaPDFError as e:
|
|
20
|
+
sys.exit(f"{e.code}: {e.message}")
|
|
21
|
+
print("Saved", ", ".join(result.saved))
|
|
22
|
+
print(result.attribution)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Send a PDF for signature to one person, then list your requests.
|
|
2
|
+
|
|
3
|
+
KOVAPDF_API_KEY=kova_test_... python examples/sign_request.py agreement.pdf "Alex Rivera" alex@example.com
|
|
4
|
+
|
|
5
|
+
A test key (kova_test_) sends every email to your own address only.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from kovapdf import KovaPDF, KovaPDFError
|
|
11
|
+
|
|
12
|
+
if len(sys.argv) < 4:
|
|
13
|
+
sys.exit('Usage: python examples/sign_request.py file.pdf "Signer Name" signer@example.com')
|
|
14
|
+
file, name, email = sys.argv[1:4]
|
|
15
|
+
|
|
16
|
+
kova = KovaPDF()
|
|
17
|
+
try:
|
|
18
|
+
request = kova.sign_requests.create(
|
|
19
|
+
file,
|
|
20
|
+
{
|
|
21
|
+
"title": "Please sign",
|
|
22
|
+
"sequential": False,
|
|
23
|
+
"signers": [{"name": name, "email": email, "order": 1}],
|
|
24
|
+
"fields": [{"signer": 0, "kind": "SIGNATURE", "page": 1, "x": 72, "y": 640, "width": 180, "height": 48}],
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
print(f"Sent request {request['id']} ({request['status']})")
|
|
28
|
+
print(f"You have {len(kova.sign_requests.list())} signature request(s).")
|
|
29
|
+
print("Done with KovaPDF (kovapdf.com).")
|
|
30
|
+
except KovaPDFError as e:
|
|
31
|
+
sys.exit(f"{e.code}: {e.message}")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""A webhook receiver that checks KovaPDF's signature before trusting an event.
|
|
2
|
+
|
|
3
|
+
KOVAPDF_WEBHOOK_SECRET=whsec_... python examples/verify_webhook.py listens on PORT (default 3000)
|
|
4
|
+
python examples/verify_webhook.py --demo signs a sample the way KovaPDF does and checks it
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
import hmac
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
14
|
+
|
|
15
|
+
from kovapdf import KovaPDFError, verify_webhook
|
|
16
|
+
|
|
17
|
+
if "--demo" in sys.argv:
|
|
18
|
+
secret = "whsec_demo_only"
|
|
19
|
+
body = json.dumps({"id": "d1", "type": "request.completed", "createdAt": "2026-11-10T10:00:00Z", "data": {"request": {"id": "sr1"}}})
|
|
20
|
+
t = int(time.time())
|
|
21
|
+
header = f"t={t},v1=" + hmac.new(secret.encode(), f"{t}.{body}".encode(), hashlib.sha256).hexdigest()
|
|
22
|
+
event = verify_webhook(body, {"KovaPDF-Signature": header}, secret)
|
|
23
|
+
print(f"Verified {event['type']} ({event['id']})")
|
|
24
|
+
try:
|
|
25
|
+
verify_webhook(body.replace("sr1", "sr2"), {"KovaPDF-Signature": header}, secret)
|
|
26
|
+
except KovaPDFError as e:
|
|
27
|
+
print(f"A changed body is refused: {e.code}")
|
|
28
|
+
sys.exit(0)
|
|
29
|
+
|
|
30
|
+
SECRET = os.environ.get("KOVAPDF_WEBHOOK_SECRET")
|
|
31
|
+
if not SECRET:
|
|
32
|
+
sys.exit("Set KOVAPDF_WEBHOOK_SECRET to the endpoint's signing secret from your dashboard.")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Receiver(BaseHTTPRequestHandler):
|
|
36
|
+
def do_POST(self) -> None:
|
|
37
|
+
raw = self.rfile.read(int(self.headers.get("Content-Length") or 0)) # the raw body, before JSON parsing
|
|
38
|
+
try:
|
|
39
|
+
event = verify_webhook(raw, dict(self.headers), SECRET)
|
|
40
|
+
except KovaPDFError:
|
|
41
|
+
self.send_response(400)
|
|
42
|
+
self.end_headers()
|
|
43
|
+
return
|
|
44
|
+
print(event["type"], event.get("data"))
|
|
45
|
+
self.send_response(200)
|
|
46
|
+
self.end_headers()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
print("Listening for KovaPDF webhooks")
|
|
50
|
+
HTTPServer(("", int(os.environ.get("PORT", "3000"))), Receiver).serve_forever()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling==1.33.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kovapdf"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "The official KovaPDF client for Python: compress, merge, split, convert, read scans, redact, check signatures and send documents for signature. Includes the kovapdf command line tool."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "KovaPDF", email = "support@kovapdf.com" }]
|
|
14
|
+
keywords = ["pdf", "kovapdf", "compress", "merge", "ocr", "redact", "e-signature", "bijoy", "unicode"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Topic :: Office/Business",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
21
|
+
dependencies = []
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://www.kovapdf.com"
|
|
25
|
+
Documentation = "https://www.kovapdf.com/developers"
|
|
26
|
+
Security = "https://www.kovapdf.com/security"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
kovapdf = "kovapdf.cli:main"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/kovapdf"]
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.sdist]
|
|
35
|
+
include = ["src/kovapdf", "tests", "examples", "README.md", "LICENSE", "SECURITY.md", "CHANGELOG.md"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""KovaPDF for Python: compress, merge, split, convert, read scans, redact,
|
|
2
|
+
check signatures and send documents for signature.
|
|
3
|
+
|
|
4
|
+
from kovapdf import KovaPDF
|
|
5
|
+
kova = KovaPDF() # the key comes from KOVAPDF_API_KEY
|
|
6
|
+
result = kova.compress("in.pdf", level="strong", save_to="out.pdf")
|
|
7
|
+
print(result.attribution) # Done with KovaPDF (kovapdf.com).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from ._generated import API_BASE, ATTRIBUTION, ENDPOINTS, SDK_VERSION, SITE_URL, TOOL_IDS, TOOLS
|
|
11
|
+
from ._http import ClientIdentity, Transport, parse_retry_after
|
|
12
|
+
from .client import KovaPDF, ResultFile, RunResult
|
|
13
|
+
from .errors import FRIENDLY, KovaPDFError
|
|
14
|
+
from .webhooks import verify_webhook
|
|
15
|
+
|
|
16
|
+
__version__ = SDK_VERSION
|
|
17
|
+
__all__ = [
|
|
18
|
+
"API_BASE",
|
|
19
|
+
"ATTRIBUTION",
|
|
20
|
+
"ENDPOINTS",
|
|
21
|
+
"FRIENDLY",
|
|
22
|
+
"SDK_VERSION",
|
|
23
|
+
"SITE_URL",
|
|
24
|
+
"TOOLS",
|
|
25
|
+
"TOOL_IDS",
|
|
26
|
+
"ClientIdentity",
|
|
27
|
+
"KovaPDF",
|
|
28
|
+
"KovaPDFError",
|
|
29
|
+
"ResultFile",
|
|
30
|
+
"RunResult",
|
|
31
|
+
"Transport",
|
|
32
|
+
"parse_retry_after",
|
|
33
|
+
"verify_webhook",
|
|
34
|
+
]
|