sharptoolz 0.2.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.
- sharptoolz-0.2.0/PKG-INFO +36 -0
- sharptoolz-0.2.0/README.md +26 -0
- sharptoolz-0.2.0/pyproject.toml +21 -0
- sharptoolz-0.2.0/setup.cfg +4 -0
- sharptoolz-0.2.0/src/sharptoolz/__init__.py +4 -0
- sharptoolz-0.2.0/src/sharptoolz/client.py +229 -0
- sharptoolz-0.2.0/src/sharptoolz/py.typed +1 -0
- sharptoolz-0.2.0/src/sharptoolz.egg-info/PKG-INFO +36 -0
- sharptoolz-0.2.0/src/sharptoolz.egg-info/SOURCES.txt +11 -0
- sharptoolz-0.2.0/src/sharptoolz.egg-info/dependency_links.txt +1 -0
- sharptoolz-0.2.0/src/sharptoolz.egg-info/requires.txt +2 -0
- sharptoolz-0.2.0/src/sharptoolz.egg-info/top_level.txt +1 -0
- sharptoolz-0.2.0/tests/test_client.py +111 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sharptoolz
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official Python client for the SharpToolz API
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: httpx<1,>=0.27
|
|
9
|
+
Requires-Dist: websockets<16,>=12
|
|
10
|
+
|
|
11
|
+
# SharpToolz Python SDK
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Available after the first PyPI release
|
|
15
|
+
pip install sharptoolz
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import os
|
|
20
|
+
from sharptoolz import SharpToolz
|
|
21
|
+
|
|
22
|
+
with SharpToolz(api_key=os.environ["SHARPTOOLZ_API_KEY"]) as sharp:
|
|
23
|
+
session = sharp.hosted_forms.create(
|
|
24
|
+
template_id=template_id,
|
|
25
|
+
external_user_id=current_user_id,
|
|
26
|
+
origin="https://app.example.com",
|
|
27
|
+
mode="test",
|
|
28
|
+
preview_mode="protected",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Return session["embed_url"] to your frontend.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Only browser JavaScript mounts the returned URL with `@sharp-toolz/sdk/browser`.
|
|
35
|
+
Python keeps the API key on your backend and can create or edit hosted sessions,
|
|
36
|
+
list documents, and wait for PNG/PDF renders over a job-scoped WebSocket.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# SharpToolz Python SDK
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
# Available after the first PyPI release
|
|
5
|
+
pip install sharptoolz
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
import os
|
|
10
|
+
from sharptoolz import SharpToolz
|
|
11
|
+
|
|
12
|
+
with SharpToolz(api_key=os.environ["SHARPTOOLZ_API_KEY"]) as sharp:
|
|
13
|
+
session = sharp.hosted_forms.create(
|
|
14
|
+
template_id=template_id,
|
|
15
|
+
external_user_id=current_user_id,
|
|
16
|
+
origin="https://app.example.com",
|
|
17
|
+
mode="test",
|
|
18
|
+
preview_mode="protected",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Return session["embed_url"] to your frontend.
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Only browser JavaScript mounts the returned URL with `@sharp-toolz/sdk/browser`.
|
|
25
|
+
Python keeps the API key on your backend and can create or edit hosted sessions,
|
|
26
|
+
list documents, and wait for PNG/PDF renders over a job-scoped WebSocket.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sharptoolz"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Official Python client for the SharpToolz API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = [
|
|
13
|
+
"httpx>=0.27,<1",
|
|
14
|
+
"websockets>=12,<16",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[tool.setuptools.packages.find]
|
|
18
|
+
where = ["src"]
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.package-data]
|
|
21
|
+
sharptoolz = ["py.typed"]
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
from urllib.parse import parse_qs, urlsplit
|
|
7
|
+
from uuid import uuid4
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from websockets.sync.client import connect as websocket_connect_default
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
DEFAULT_BASE_URL = "https://api.sharptoolz.com/api/v1"
|
|
14
|
+
TERMINAL_RENDER_STATUSES = {"completed", "failed"}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _normalize_cursor(cursor: str | None) -> str | None:
|
|
18
|
+
if not cursor:
|
|
19
|
+
return None
|
|
20
|
+
parsed = urlsplit(cursor)
|
|
21
|
+
if parsed.scheme and parsed.netloc:
|
|
22
|
+
return parse_qs(parsed.query).get("cursor", [cursor])[0]
|
|
23
|
+
return cursor
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SharpToolzError(RuntimeError):
|
|
27
|
+
def __init__(self, message: str, *, status_code: int = 0, data: Any = None):
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
self.status_code = status_code
|
|
30
|
+
self.data = data
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TemplatesResource:
|
|
34
|
+
def __init__(self, client: "SharpToolz"):
|
|
35
|
+
self._client = client
|
|
36
|
+
|
|
37
|
+
def list(self) -> dict[str, Any]:
|
|
38
|
+
return self._client._request("GET", "/templates")
|
|
39
|
+
|
|
40
|
+
class HostedFormsResource:
|
|
41
|
+
def __init__(self, client: "SharpToolz"):
|
|
42
|
+
self._client = client
|
|
43
|
+
|
|
44
|
+
def create(self, **payload: Any) -> dict[str, Any]:
|
|
45
|
+
return self._client._request("POST", "/embed-sessions", json=payload)
|
|
46
|
+
|
|
47
|
+
def edit(self, document_id: str, **payload: Any) -> dict[str, Any]:
|
|
48
|
+
return self._client._request("POST", f"/documents/{document_id}/session", json=payload)
|
|
49
|
+
|
|
50
|
+
def revoke(self, session_id: str) -> None:
|
|
51
|
+
self._client._request("DELETE", f"/embed-sessions/{session_id}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DocumentsResource:
|
|
55
|
+
def __init__(self, client: "SharpToolz"):
|
|
56
|
+
self._client = client
|
|
57
|
+
|
|
58
|
+
def get(self, document_id: str) -> dict[str, Any]:
|
|
59
|
+
return self._client._request("GET", f"/documents/{document_id}")
|
|
60
|
+
|
|
61
|
+
def list(self, *, external_user_id: str | None = None, cursor: str | None = None) -> dict[str, Any]:
|
|
62
|
+
params = {key: value for key, value in {
|
|
63
|
+
"external_user_id": external_user_id,
|
|
64
|
+
"cursor": _normalize_cursor(cursor),
|
|
65
|
+
}.items() if value}
|
|
66
|
+
return self._client._request("GET", "/documents", params=params)
|
|
67
|
+
|
|
68
|
+
def delete(self, document_id: str) -> None:
|
|
69
|
+
self._client._request("DELETE", f"/documents/{document_id}")
|
|
70
|
+
|
|
71
|
+
def upgrade(self, document_id: str, *, idempotency_key: str | None = None) -> dict[str, Any]:
|
|
72
|
+
return self._client._request(
|
|
73
|
+
"POST",
|
|
74
|
+
f"/documents/{document_id}/upgrade",
|
|
75
|
+
idempotency_key=idempotency_key or str(uuid4()),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def render(
|
|
79
|
+
self,
|
|
80
|
+
document_id: str,
|
|
81
|
+
*,
|
|
82
|
+
format: str = "pdf",
|
|
83
|
+
idempotency_key: str | None = None,
|
|
84
|
+
) -> dict[str, Any]:
|
|
85
|
+
return self._client._request(
|
|
86
|
+
"POST",
|
|
87
|
+
f"/documents/{document_id}/render",
|
|
88
|
+
json={"format": format},
|
|
89
|
+
idempotency_key=idempotency_key or str(uuid4()),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def render_and_wait(
|
|
93
|
+
self,
|
|
94
|
+
document_id: str,
|
|
95
|
+
*,
|
|
96
|
+
format: str = "pdf",
|
|
97
|
+
idempotency_key: str | None = None,
|
|
98
|
+
timeout: float = 120,
|
|
99
|
+
poll_fallback: bool = True,
|
|
100
|
+
) -> dict[str, Any]:
|
|
101
|
+
job = self.render(document_id, format=format, idempotency_key=idempotency_key)
|
|
102
|
+
return self._client.renders.wait(
|
|
103
|
+
job,
|
|
104
|
+
timeout=timeout,
|
|
105
|
+
poll_fallback=poll_fallback,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class RendersResource:
|
|
110
|
+
def __init__(self, client: "SharpToolz"):
|
|
111
|
+
self._client = client
|
|
112
|
+
|
|
113
|
+
def get(self, job_id: str) -> dict[str, Any]:
|
|
114
|
+
return self._client._request("GET", f"/renders/{job_id}")
|
|
115
|
+
|
|
116
|
+
def wait(
|
|
117
|
+
self,
|
|
118
|
+
job_or_id: dict[str, Any] | str,
|
|
119
|
+
*,
|
|
120
|
+
timeout: float = 120,
|
|
121
|
+
poll_fallback: bool = True,
|
|
122
|
+
) -> dict[str, Any]:
|
|
123
|
+
job = self.get(job_or_id) if isinstance(job_or_id, str) else job_or_id
|
|
124
|
+
if not job.get("id"):
|
|
125
|
+
raise TypeError("A render job or job ID is required.")
|
|
126
|
+
if job.get("status") in TERMINAL_RENDER_STATUSES:
|
|
127
|
+
return self._finish(job)
|
|
128
|
+
|
|
129
|
+
watch = self._client._request("POST", f"/renders/{job['id']}/watch")
|
|
130
|
+
try:
|
|
131
|
+
terminal = self._wait_on_websocket(watch["websocket_url"], timeout=timeout)
|
|
132
|
+
return self._finish(self.get(terminal["id"]))
|
|
133
|
+
except Exception:
|
|
134
|
+
if not poll_fallback:
|
|
135
|
+
raise
|
|
136
|
+
return self._wait_by_polling(job["id"], timeout=timeout)
|
|
137
|
+
|
|
138
|
+
def _wait_on_websocket(self, url: str, *, timeout: float) -> dict[str, Any]:
|
|
139
|
+
deadline = time.monotonic() + timeout
|
|
140
|
+
with self._client._websocket_connect(url, open_timeout=min(10, timeout)) as socket:
|
|
141
|
+
while True:
|
|
142
|
+
remaining = deadline - time.monotonic()
|
|
143
|
+
if remaining <= 0:
|
|
144
|
+
raise SharpToolzError("Render wait timed out.")
|
|
145
|
+
raw = socket.recv(timeout=remaining)
|
|
146
|
+
message = json.loads(raw)
|
|
147
|
+
data = message.get("data") if message.get("type") == "render.updated" else None
|
|
148
|
+
if data and data.get("status") in TERMINAL_RENDER_STATUSES:
|
|
149
|
+
return data
|
|
150
|
+
|
|
151
|
+
def _wait_by_polling(self, job_id: str, *, timeout: float) -> dict[str, Any]:
|
|
152
|
+
deadline = time.monotonic() + timeout
|
|
153
|
+
interval = 0.75
|
|
154
|
+
while time.monotonic() < deadline:
|
|
155
|
+
time.sleep(interval)
|
|
156
|
+
job = self.get(job_id)
|
|
157
|
+
if job.get("status") in TERMINAL_RENDER_STATUSES:
|
|
158
|
+
return self._finish(job)
|
|
159
|
+
interval = min(interval * 1.5, 4)
|
|
160
|
+
raise SharpToolzError("Render wait timed out.")
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def _finish(job: dict[str, Any]) -> dict[str, Any]:
|
|
164
|
+
if job.get("status") == "failed":
|
|
165
|
+
code = job.get("error_code")
|
|
166
|
+
raise SharpToolzError(f"Render failed{f': {code}' if code else '.'}", data=job)
|
|
167
|
+
return job
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class SharpToolz:
|
|
171
|
+
def __init__(
|
|
172
|
+
self,
|
|
173
|
+
*,
|
|
174
|
+
api_key: str,
|
|
175
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
176
|
+
timeout: float = 30,
|
|
177
|
+
transport: httpx.BaseTransport | None = None,
|
|
178
|
+
websocket_connect: Callable[..., Any] = websocket_connect_default,
|
|
179
|
+
):
|
|
180
|
+
if not isinstance(api_key, str) or not api_key.startswith("stz_live_"):
|
|
181
|
+
raise TypeError("A SharpToolz server API key is required.")
|
|
182
|
+
self._http = httpx.Client(
|
|
183
|
+
base_url=base_url.rstrip("/"),
|
|
184
|
+
timeout=timeout,
|
|
185
|
+
transport=transport,
|
|
186
|
+
headers={
|
|
187
|
+
"Authorization": f"Bearer {api_key}",
|
|
188
|
+
"Accept": "application/json",
|
|
189
|
+
"User-Agent": "sharptoolz-python/0.2.0",
|
|
190
|
+
},
|
|
191
|
+
)
|
|
192
|
+
self._websocket_connect = websocket_connect
|
|
193
|
+
self.templates = TemplatesResource(self)
|
|
194
|
+
self.hosted_forms = HostedFormsResource(self)
|
|
195
|
+
self.documents = DocumentsResource(self)
|
|
196
|
+
self.renders = RendersResource(self)
|
|
197
|
+
|
|
198
|
+
def __enter__(self) -> "SharpToolz":
|
|
199
|
+
return self
|
|
200
|
+
|
|
201
|
+
def __exit__(self, exc_type, exc, traceback) -> None:
|
|
202
|
+
self.close()
|
|
203
|
+
|
|
204
|
+
def close(self) -> None:
|
|
205
|
+
self._http.close()
|
|
206
|
+
|
|
207
|
+
def _request(
|
|
208
|
+
self,
|
|
209
|
+
method: str,
|
|
210
|
+
path: str,
|
|
211
|
+
*,
|
|
212
|
+
json: dict[str, Any] | None = None,
|
|
213
|
+
params: dict[str, Any] | None = None,
|
|
214
|
+
idempotency_key: str | None = None,
|
|
215
|
+
) -> Any:
|
|
216
|
+
headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
|
|
217
|
+
response = self._http.request(method, path, json=json, params=params, headers=headers)
|
|
218
|
+
if response.status_code == 204:
|
|
219
|
+
return None
|
|
220
|
+
try:
|
|
221
|
+
data = response.json()
|
|
222
|
+
except ValueError:
|
|
223
|
+
data = None
|
|
224
|
+
if response.is_error:
|
|
225
|
+
message = (
|
|
226
|
+
data.get("detail") if isinstance(data, dict) else None
|
|
227
|
+
) or f"SharpToolz request failed with HTTP {response.status_code}."
|
|
228
|
+
raise SharpToolzError(message, status_code=response.status_code, data=data)
|
|
229
|
+
return data
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sharptoolz
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official Python client for the SharpToolz API
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: httpx<1,>=0.27
|
|
9
|
+
Requires-Dist: websockets<16,>=12
|
|
10
|
+
|
|
11
|
+
# SharpToolz Python SDK
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Available after the first PyPI release
|
|
15
|
+
pip install sharptoolz
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import os
|
|
20
|
+
from sharptoolz import SharpToolz
|
|
21
|
+
|
|
22
|
+
with SharpToolz(api_key=os.environ["SHARPTOOLZ_API_KEY"]) as sharp:
|
|
23
|
+
session = sharp.hosted_forms.create(
|
|
24
|
+
template_id=template_id,
|
|
25
|
+
external_user_id=current_user_id,
|
|
26
|
+
origin="https://app.example.com",
|
|
27
|
+
mode="test",
|
|
28
|
+
preview_mode="protected",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Return session["embed_url"] to your frontend.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Only browser JavaScript mounts the returned URL with `@sharp-toolz/sdk/browser`.
|
|
35
|
+
Python keeps the API key on your backend and can create or edit hosted sessions,
|
|
36
|
+
list documents, and wait for PNG/PDF renders over a job-scoped WebSocket.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/sharptoolz/__init__.py
|
|
4
|
+
src/sharptoolz/client.py
|
|
5
|
+
src/sharptoolz/py.typed
|
|
6
|
+
src/sharptoolz.egg-info/PKG-INFO
|
|
7
|
+
src/sharptoolz.egg-info/SOURCES.txt
|
|
8
|
+
src/sharptoolz.egg-info/dependency_links.txt
|
|
9
|
+
src/sharptoolz.egg-info/requires.txt
|
|
10
|
+
src/sharptoolz.egg-info/top_level.txt
|
|
11
|
+
tests/test_client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sharptoolz
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from sharptoolz import SharpToolz
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FakeSocket:
|
|
10
|
+
def __enter__(self):
|
|
11
|
+
return self
|
|
12
|
+
|
|
13
|
+
def __exit__(self, exc_type, exc, traceback):
|
|
14
|
+
return None
|
|
15
|
+
|
|
16
|
+
def recv(self, timeout=None):
|
|
17
|
+
return json.dumps({
|
|
18
|
+
"type": "render.updated",
|
|
19
|
+
"data": {"id": "job-1", "status": "completed"},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SharpToolzClientTests(unittest.TestCase):
|
|
24
|
+
def test_api_key_stays_in_authorization_header(self):
|
|
25
|
+
captured = {}
|
|
26
|
+
|
|
27
|
+
def handler(request):
|
|
28
|
+
captured["request"] = request
|
|
29
|
+
return httpx.Response(200, json={"results": []})
|
|
30
|
+
|
|
31
|
+
with SharpToolz(
|
|
32
|
+
api_key="stz_live_test.key",
|
|
33
|
+
transport=httpx.MockTransport(handler),
|
|
34
|
+
) as client:
|
|
35
|
+
client.templates.list()
|
|
36
|
+
|
|
37
|
+
request = captured["request"]
|
|
38
|
+
self.assertEqual(request.headers["Authorization"], "Bearer stz_live_test.key")
|
|
39
|
+
self.assertNotIn("stz_live_test.key", str(request.url))
|
|
40
|
+
|
|
41
|
+
def test_render_wait_uses_websocket_then_fetches_final_job_once(self):
|
|
42
|
+
calls = []
|
|
43
|
+
|
|
44
|
+
def handler(request):
|
|
45
|
+
calls.append((request.method, request.url.path))
|
|
46
|
+
if request.url.path.endswith("/watch"):
|
|
47
|
+
return httpx.Response(200, json={"websocket_url": "wss://api.sharptoolz.com/ws/job"})
|
|
48
|
+
return httpx.Response(200, json={
|
|
49
|
+
"id": "job-1",
|
|
50
|
+
"status": "completed",
|
|
51
|
+
"download_url": "https://signed.example/file",
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
with SharpToolz(
|
|
55
|
+
api_key="stz_live_test.key",
|
|
56
|
+
transport=httpx.MockTransport(handler),
|
|
57
|
+
websocket_connect=lambda *args, **kwargs: FakeSocket(),
|
|
58
|
+
) as client:
|
|
59
|
+
result = client.renders.wait({"id": "job-1", "status": "queued"})
|
|
60
|
+
|
|
61
|
+
self.assertEqual(result["download_url"], "https://signed.example/file")
|
|
62
|
+
self.assertEqual(calls, [
|
|
63
|
+
("POST", "/api/v1/renders/job-1/watch"),
|
|
64
|
+
("GET", "/api/v1/renders/job-1"),
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
def test_creation_and_editing_are_hosted_session_only(self):
|
|
68
|
+
calls = []
|
|
69
|
+
|
|
70
|
+
def handler(request):
|
|
71
|
+
calls.append((request.method, request.url.path))
|
|
72
|
+
return httpx.Response(201, json={"id": "session-1"})
|
|
73
|
+
|
|
74
|
+
with SharpToolz(
|
|
75
|
+
api_key="stz_live_test.key",
|
|
76
|
+
transport=httpx.MockTransport(handler),
|
|
77
|
+
) as client:
|
|
78
|
+
client.hosted_forms.create(
|
|
79
|
+
template_id="template-1",
|
|
80
|
+
external_user_id="user-1",
|
|
81
|
+
origin="https://app.example",
|
|
82
|
+
)
|
|
83
|
+
client.hosted_forms.edit("document-1", origin="https://app.example")
|
|
84
|
+
self.assertFalse(hasattr(client.templates, "schema"))
|
|
85
|
+
self.assertFalse(hasattr(client.documents, "create"))
|
|
86
|
+
|
|
87
|
+
self.assertEqual(calls, [
|
|
88
|
+
("POST", "/api/v1/embed-sessions"),
|
|
89
|
+
("POST", "/api/v1/documents/document-1/session"),
|
|
90
|
+
])
|
|
91
|
+
|
|
92
|
+
def test_document_list_accepts_api_next_url_as_cursor(self):
|
|
93
|
+
captured = {}
|
|
94
|
+
|
|
95
|
+
def handler(request):
|
|
96
|
+
captured["query"] = request.url.query.decode()
|
|
97
|
+
return httpx.Response(200, json={"results": [], "next": None, "previous": None})
|
|
98
|
+
|
|
99
|
+
with SharpToolz(
|
|
100
|
+
api_key="stz_live_test.key",
|
|
101
|
+
transport=httpx.MockTransport(handler),
|
|
102
|
+
) as client:
|
|
103
|
+
client.documents.list(
|
|
104
|
+
cursor="https://api.sharptoolz.com/api/v1/documents?cursor=cD0yMDI2"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
self.assertEqual(captured["query"], "cursor=cD0yMDI2")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
unittest.main()
|