Skip to content

Commit 0883631

Browse files
authored
Add HTML to PDF support (anvilco#6)
* Add HTML-to-PDF support * Update docs * Bump version * More validations, tests
1 parent db1034c commit 0883631

File tree

6 files changed

+97
-6
lines changed

6 files changed

+97
-6
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
# 0.1.1 (2021-02-16)
1+
# 0.2.0 (2021-05-05)
2+
3+
- Add support for HTML to PDF on `generate_pdf`
24

3-
* Fix for REST API calls failing
5+
# 0.1.1 (2021-02-16)
46

7+
- Fix for REST API calls failing
58

69
# 0.1.0 (2021-01-30)
710

docs/api_usage.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,28 @@ response = anvil.fill_pdf("some_template", data)
3737
Anvil allows you to dynamically generate new PDFs using JSON data you provide via the /api/v1/generate-pdf REST
3838
endpoint. Useful for agreements, invoices, disclosures, or any other text-heavy documents.
3939

40+
By default, `generate_pdf` will format data assuming it's in [Markdown](https://daringfireball.net/projects/markdown/).
41+
42+
HTML is another supported input type. This can be used by providing
43+
`"type": "html"` in the payload and making the `data` field a dict containing
44+
keys `"html"` and an optional `"css"`. Example below:
45+
46+
```python
47+
anvil = Anvil(api_key="MY KEY")
48+
data = {
49+
"type": "html",
50+
"title": "Some Title",
51+
"data": {
52+
"html": "<h2>HTML Heading</h2>"
53+
"css": "h2 { color: red }"
54+
}
55+
}
56+
response = anvil.generate_pdf(data)
57+
```
58+
59+
See the official [Anvil Docs on HTML to PDF](https://www.useanvil.com/docs/api/generate-pdf#html--css-to-pdf)
60+
for more details.
61+
4062
<strong>payload: Union[dict, AnyStr, GeneratePDFPayload]</strong>
4163

4264
Data to embed into the PDF. Supported `payload` types are:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tool.poetry]
22

33
name = "python_anvil"
4-
version = "0.1.1"
4+
version = "0.2.0"
55
description = "Anvil API"
66

77
license = "MIT"

python_anvil/api.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,27 @@ def generate_pdf(self, payload: Union[AnyStr, Dict]):
8585
else:
8686
raise ValueError("`payload` must be a valid JSON string or a dict")
8787

88+
# `dataclasses_json` doesn't seem to validate the `type` field here,
89+
# so double-check that.
90+
if data.type and data.type.lower() not in ["html", "markdown"]:
91+
raise ValueError("`type` must be either 'html' or 'markdown'")
92+
93+
# For HTML-type payloads, data must be a dict (or JSON object).
94+
if data.type == "html":
95+
if not isinstance(data.data, dict):
96+
raise ValueError(
97+
"`payload` data must be a dict when generating a PDF from HTML"
98+
)
99+
if "html" not in data.data:
100+
raise ValueError(
101+
"`payload` data must have 'html' if using the 'html' type"
102+
)
103+
elif data.type == 'markdown' and not isinstance(data.data, list):
104+
raise ValueError(
105+
"`payload` data must be a list of dicts when generating a PDF "
106+
"from markdown (this is the default)"
107+
)
108+
88109
# Any data errors would come from here..
89110
api = RestRequest(client=self.client)
90111
return api.post("generate-pdf", data=remove_empty_items(data.to_dict()))

python_anvil/api_resources/payload.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ class FillPDFPayload(DataClassJsonMixin):
3131
@dataclass_json(letter_case=LetterCase.CAMEL)
3232
@dataclass
3333
class GeneratePDFPayload(DataClassJsonMixin):
34-
data: List[Dict[str, Any]]
34+
data: Union[List[Dict[str, Any]], Dict[str, Any]]
3535
logo: Optional[EmbeddedLogo] = None
3636
title: Optional[str] = None
37+
type: Optional[str] = "markdown" # or html
3738

3839

3940
@dataclass_json(letter_case=LetterCase.CAMEL)

python_anvil/tests/test_api.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,61 @@ def describe_generate_pdf():
5252
def test_dict_payload(m_request_post, anvil):
5353
anvil.generate_pdf({"data": [{"d1": "data"}]})
5454
m_request_post.assert_called_once_with(
55-
"generate-pdf", data={'data': [{'d1': 'data'}]}
55+
# Defaults to 'markdown'
56+
"generate-pdf",
57+
data={'data': [{'d1': 'data'}], 'type': 'markdown'},
5658
)
5759

5860
@mock.patch('python_anvil.api.RestRequest.post')
5961
def test_json_payload(m_request_post, anvil):
6062
payload = """{ "data": [{ "d1": "data" }] }"""
6163
anvil.generate_pdf(payload)
6264
m_request_post.assert_called_once_with(
63-
"generate-pdf", data={'data': [{'d1': 'data'}]}
65+
"generate-pdf", data={"data": [{"d1": "data"}], "type": "markdown"}
6466
)
6567

68+
@mock.patch('python_anvil.api.RestRequest.post')
69+
def test_payload_html_type(m_request_post, anvil):
70+
anvil.generate_pdf({"data": {"html": "<h1>Hello</h1>"}, "type": "html"})
71+
m_request_post.assert_called_once_with(
72+
"generate-pdf",
73+
data={"data": {"html": "<h1>Hello</h1>"}, "type": "html"},
74+
)
75+
76+
@mock.patch('python_anvil.api.RestRequest.post')
77+
def test_invalid_payload_html_payload(m_request_post, anvil):
78+
with pytest.raises(ValueError):
79+
anvil.generate_pdf({"data": {"no_html_here": "Nope"}, "type": "html"})
80+
81+
@mock.patch('python_anvil.api.RestRequest.post')
82+
def test_payload_invalid_type(m_request_post, anvil):
83+
with pytest.raises(ValueError):
84+
anvil.generate_pdf(
85+
{"data": [{"d1": "data"}], "type": "something_invalid"}
86+
)
87+
88+
@mock.patch('python_anvil.api.RestRequest.post')
89+
def test_invalid_data_for_html(m_request_post, anvil):
90+
with pytest.raises(ValueError):
91+
anvil.generate_pdf(
92+
{
93+
# This should be a plain dict, not a list
94+
"data": [{"d1": "data"}],
95+
"type": "html",
96+
}
97+
)
98+
99+
@mock.patch('python_anvil.api.RestRequest.post')
100+
def test_invalid_data_for_markdown(m_request_post, anvil):
101+
with pytest.raises(ValueError):
102+
anvil.generate_pdf(
103+
{
104+
# This should be a plain dict, not a list
105+
"data": {"d1": "data"},
106+
"type": "markdown",
107+
}
108+
)
109+
66110
def describe_current_user_query():
67111
@mock.patch('python_anvil.api.GraphqlRequest.post')
68112
def test_get_current_user(m_request_post, anvil):

0 commit comments

Comments
 (0)