Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled
- Node.js SDK (sdk/nodejs/): TypeScript, zero deps, native fetch, Node 18+ - Python SDK (sdk/python/): sync + async clients via httpx, Python 3.8+ - Both wrap all conversion endpoints (html, markdown, url, templates) - Proper error handling with DocFastError - Full README documentation for each
148 lines
4.9 KiB
Python
148 lines
4.9 KiB
Python
"""DocFast API clients (sync and async)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
from urllib.parse import quote
|
|
|
|
import httpx
|
|
|
|
|
|
class DocFastError(Exception):
|
|
"""Error returned by the DocFast API."""
|
|
|
|
def __init__(self, message: str, status: int, code: Optional[str] = None):
|
|
super().__init__(message)
|
|
self.status = status
|
|
self.code = code
|
|
|
|
|
|
_KEY_MAP = {"print_background": "printBackground"}
|
|
|
|
|
|
def _build_body(key: str, value: str, options: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
body: Dict[str, Any] = {key: value}
|
|
if options:
|
|
body["options"] = {_KEY_MAP.get(k, k): v for k, v in options.items()}
|
|
return body
|
|
|
|
|
|
def _handle_error(response: httpx.Response) -> None:
|
|
if response.is_success:
|
|
return
|
|
message = f"HTTP {response.status_code}"
|
|
code = None
|
|
try:
|
|
data = response.json()
|
|
if "error" in data:
|
|
message = data["error"]
|
|
code = data.get("code")
|
|
except Exception:
|
|
pass
|
|
raise DocFastError(message, response.status_code, code)
|
|
|
|
|
|
class DocFast:
|
|
"""Synchronous DocFast client."""
|
|
|
|
def __init__(self, api_key: str, *, base_url: Optional[str] = None):
|
|
if not api_key:
|
|
raise ValueError("API key is required")
|
|
self._base_url = (base_url or "https://docfast.dev").rstrip("/")
|
|
self._client = httpx.Client(
|
|
base_url=self._base_url,
|
|
headers={"Authorization": f"Bearer {api_key}"},
|
|
timeout=120.0,
|
|
)
|
|
|
|
def __enter__(self) -> "DocFast":
|
|
return self
|
|
|
|
def __exit__(self, *args: Any) -> None:
|
|
self.close()
|
|
|
|
def close(self) -> None:
|
|
self._client.close()
|
|
|
|
def _convert(self, path: str, body: Dict[str, Any]) -> bytes:
|
|
r = self._client.post(path, json=body)
|
|
_handle_error(r)
|
|
return r.content
|
|
|
|
def html(self, html: str, **options: Any) -> bytes:
|
|
"""Convert HTML to PDF."""
|
|
return self._convert("/v1/convert/html", _build_body("html", html, options or None))
|
|
|
|
def markdown(self, markdown: str, **options: Any) -> bytes:
|
|
"""Convert Markdown to PDF."""
|
|
return self._convert("/v1/convert/markdown", _build_body("markdown", markdown, options or None))
|
|
|
|
def url(self, url: str, **options: Any) -> bytes:
|
|
"""Convert a URL to PDF."""
|
|
return self._convert("/v1/convert/url", _build_body("url", url, options or None))
|
|
|
|
def templates(self) -> List[Dict[str, Any]]:
|
|
"""List available templates."""
|
|
r = self._client.get("/v1/templates")
|
|
_handle_error(r)
|
|
return r.json()
|
|
|
|
def render_template(self, template_id: str, data: Dict[str, Any], **options: Any) -> bytes:
|
|
"""Render a template to PDF."""
|
|
body: Dict[str, Any] = {"data": data}
|
|
if options:
|
|
body["options"] = options
|
|
return self._convert(f"/v1/templates/{quote(template_id, safe='')}/render", body)
|
|
|
|
|
|
class AsyncDocFast:
|
|
"""Asynchronous DocFast client."""
|
|
|
|
def __init__(self, api_key: str, *, base_url: Optional[str] = None):
|
|
if not api_key:
|
|
raise ValueError("API key is required")
|
|
self._base_url = (base_url or "https://docfast.dev").rstrip("/")
|
|
self._client = httpx.AsyncClient(
|
|
base_url=self._base_url,
|
|
headers={"Authorization": f"Bearer {api_key}"},
|
|
timeout=120.0,
|
|
)
|
|
|
|
async def __aenter__(self) -> "AsyncDocFast":
|
|
return self
|
|
|
|
async def __aexit__(self, *args: Any) -> None:
|
|
await self.close()
|
|
|
|
async def close(self) -> None:
|
|
await self._client.aclose()
|
|
|
|
async def _convert(self, path: str, body: Dict[str, Any]) -> bytes:
|
|
r = await self._client.post(path, json=body)
|
|
_handle_error(r)
|
|
return r.content
|
|
|
|
async def html(self, html: str, **options: Any) -> bytes:
|
|
"""Convert HTML to PDF."""
|
|
return await self._convert("/v1/convert/html", _build_body("html", html, options or None))
|
|
|
|
async def markdown(self, markdown: str, **options: Any) -> bytes:
|
|
"""Convert Markdown to PDF."""
|
|
return await self._convert("/v1/convert/markdown", _build_body("markdown", markdown, options or None))
|
|
|
|
async def url(self, url: str, **options: Any) -> bytes:
|
|
"""Convert a URL to PDF."""
|
|
return await self._convert("/v1/convert/url", _build_body("url", url, options or None))
|
|
|
|
async def templates(self) -> List[Dict[str, Any]]:
|
|
"""List available templates."""
|
|
r = await self._client.get("/v1/templates")
|
|
_handle_error(r)
|
|
return r.json()
|
|
|
|
async def render_template(self, template_id: str, data: Dict[str, Any], **options: Any) -> bytes:
|
|
"""Render a template to PDF."""
|
|
body: Dict[str, Any] = {"data": data}
|
|
if options:
|
|
body["options"] = options
|
|
return await self._convert(f"/v1/templates/{quote(template_id, safe='')}/render", body)
|