Skip to content

Commit 85552ad

Browse files
authored
Support HTML/CSS in createEtchPacket (anvilco#39)
* Add support for markup documents for createEtchPacket, examples * Fix doc lint * Add markdown, generate examples. Update interfaces * Minor README update * Bump version
1 parent 91e2180 commit 85552ad

File tree

11 files changed

+400
-9
lines changed

11 files changed

+400
-9
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 1.5.0 (2022-09-07)
2+
3+
- Added support for HTML/CSS and Markdown in `CreateEtchPacket`. [See examples here](https://www.useanvil.com/docs/api/e-signatures#generating-a-pdf-from-html-and-css).
4+
15
# 1.5.0 (2022-08-05)
26

37
- Added support for `ForgeSubmit` mutation.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ General API documentation: [Anvil API docs](https://www.useanvil.com/docs)
3232

3333
Install it directly into an activated virtual environment:
3434

35-
```text
35+
```shell
3636
$ pip install python-anvil
3737
```
3838

3939
or add it to your [Poetry](https://python-poetry.org/) project:
4040

41-
```text
41+
```shell
4242
$ poetry add python-anvil
4343
```

docs/api_usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
All methods assume that a valid API key is already available. Please take a look
44
at [Anvil API Basics](https://www.useanvil.com/docs/api/basics) for more details on how to get your key.
55

6-
### Anvil() constructor
6+
### `Anvil` constructor
77

88
* `api_key` - Your Anvil API key, either development or production
99
* `environment` (default: `'dev'`) - The type of key being used. This affects how the library sets rate limits on API

examples/create_etch_markdown.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# pylint: disable=duplicate-code
2+
3+
from python_anvil.api import Anvil
4+
from python_anvil.api_resources.mutations.create_etch_packet import CreateEtchPacket
5+
from python_anvil.api_resources.payload import (
6+
DocumentMarkdown,
7+
EtchSigner,
8+
MarkdownContent,
9+
SignatureField,
10+
SignerField,
11+
)
12+
13+
14+
API_KEY = 'my-api-key'
15+
16+
17+
def main():
18+
anvil = Anvil(api_key=API_KEY)
19+
20+
# Create an instance of the builder
21+
packet = CreateEtchPacket(
22+
name="Etch packet with existing template",
23+
#
24+
# Optional changes to email subject and body content
25+
signature_email_subject="Please sign these forms",
26+
signature_email_body="This form requires information from your driver's "
27+
"license. Please have that available.",
28+
#
29+
# URL where Anvil will send POST requests when server events happen.
30+
# Take a look at https://www.useanvil.com/docs/api/e-signatures#webhook-notifications
31+
# for other details on how to configure webhooks on your account.
32+
# You can also use sites like webhook.site, requestbin.com or ngrok to
33+
# test webhooks.
34+
# webhook_url="https://my.webhook.example.com/etch-events/",
35+
#
36+
# Email overrides for the "reply-to" email header for signer emails.
37+
# If used, both `reply_to_email` and `reply_to_name` are required.
38+
# By default, this will point to your organization support email.
39+
# reply_to_email="[email protected]",
40+
# reply_to_name="My Name",
41+
#
42+
# Merge all PDFs into one. Use this if you have many PDF templates
43+
# and/or files, but want the final downloaded package to be only
44+
# 1 PDF file.
45+
# merge_pdfs=True,
46+
)
47+
48+
# Get your file(s) ready to sign.
49+
# For this example, a PDF will not be uploaded. We'll create and style the
50+
# document with HTML and CSS and add signing fields based on coordinates.
51+
52+
# Define the document with Markdown
53+
file1 = DocumentMarkdown(
54+
id="markdownFile",
55+
filename="markdown.pdf",
56+
title="Sign this markdown file",
57+
fields=[
58+
# This is markdown content
59+
MarkdownContent(
60+
table=dict(
61+
rows=[
62+
['Description', 'Quantity', 'Price'],
63+
['3x Roof Shingles', '15', '$60.00'],
64+
['5x Hardwood Plywood', '10', '$300.00'],
65+
['80x Wood Screws', '80', '$45.00'],
66+
],
67+
)
68+
),
69+
SignatureField(
70+
page_num=0,
71+
id="sign1",
72+
type="signature",
73+
# The position and size of the field. The coordinates provided here
74+
# (x=300, y=300) is the top-left of the rectangle.
75+
rect=dict(x=300, y=300, width=250, height=30),
76+
),
77+
],
78+
)
79+
80+
# Gather your signer data
81+
signer1 = EtchSigner(
82+
name="Jackie",
83+
84+
# Fields where the signer needs to sign.
85+
# Check your cast fields via the CLI (`anvil cast [cast_eid]`) or the
86+
# PDF Templates section on the Anvil app.
87+
# This basically says: "In the 'myNewFile' file (defined in
88+
# `file1` above), assign the signature field with cast id of
89+
# 'sign1' to this signer." You can add multiple signer fields here.
90+
fields=[
91+
SignerField(
92+
# this is the `id` in the `DocumentUpload` object above
93+
file_id="markdownFile",
94+
# This is the signing field id in the `SignatureField` above
95+
field_id="sign1",
96+
),
97+
],
98+
signer_type="embedded",
99+
#
100+
# You can also change how signatures will be collected.
101+
# "draw" will allow the signer to draw their signature
102+
# "text" will insert a text version of the signer's name into the
103+
# signature field.
104+
# signature_mode="draw",
105+
#
106+
# Whether or not to the signer is required to click each signature
107+
# field manually. If `False`, the PDF will be signed once the signer
108+
# accepts the PDF without making the user go through the PDF.
109+
# accept_each_field=False,
110+
#
111+
# URL of where the signer will be redirected after signing.
112+
# The URL will also have certain URL params added on, so the page
113+
# can be customized based on the signing action.
114+
# redirect_url="https://www.google.com",
115+
)
116+
117+
# Add your signer.
118+
packet.add_signer(signer1)
119+
120+
# Add files to your payload
121+
packet.add_file(file1)
122+
123+
# If needed, you can also override or add additional payload fields this way.
124+
# This is useful if the Anvil API has new features, but `python-anvil` has not
125+
# yet been updated to support it.
126+
# payload = packet.create_payload()
127+
# payload.aNewFeature = True
128+
129+
# Create your packet
130+
# If overriding/adding new fields, use the modified payload from
131+
# `packet.create_payload()`
132+
res = anvil.create_etch_packet(payload=packet, include_headers=True)
133+
print(res)
134+
135+
136+
if __name__ == '__main__':
137+
main()

examples/create_etch_markup.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# pylint: disable=duplicate-code
2+
3+
from python_anvil.api import Anvil
4+
from python_anvil.api_resources.mutations.create_etch_packet import CreateEtchPacket
5+
from python_anvil.api_resources.payload import (
6+
DocumentMarkup,
7+
EtchSigner,
8+
SignatureField,
9+
SignerField,
10+
)
11+
12+
13+
API_KEY = 'my-api-key'
14+
15+
16+
def main():
17+
anvil = Anvil(api_key=API_KEY)
18+
19+
# Create an instance of the builder
20+
packet = CreateEtchPacket(
21+
name="Etch packet with existing template",
22+
#
23+
# Optional changes to email subject and body content
24+
signature_email_subject="Please sign these forms",
25+
signature_email_body="This form requires information from your driver's "
26+
"license. Please have that available.",
27+
#
28+
# URL where Anvil will send POST requests when server events happen.
29+
# Take a look at https://www.useanvil.com/docs/api/e-signatures#webhook-notifications
30+
# for other details on how to configure webhooks on your account.
31+
# You can also use sites like webhook.site, requestbin.com or ngrok to
32+
# test webhooks.
33+
# webhook_url="https://my.webhook.example.com/etch-events/",
34+
#
35+
# Email overrides for the "reply-to" email header for signer emails.
36+
# If used, both `reply_to_email` and `reply_to_name` are required.
37+
# By default, this will point to your organization support email.
38+
# reply_to_email="[email protected]",
39+
# reply_to_name="My Name",
40+
#
41+
# Merge all PDFs into one. Use this if you have many PDF templates
42+
# and/or files, but want the final downloaded package to be only
43+
# 1 PDF file.
44+
# merge_pdfs=True,
45+
)
46+
47+
# Get your file(s) ready to sign.
48+
# For this example, a PDF will not be uploaded. We'll create and style the
49+
# document with HTML and CSS and add signing fields based on coordinates.
50+
51+
# Define the document with HTML/CSS
52+
file1 = DocumentMarkup(
53+
id="myNewFile",
54+
title="Please sign this important form",
55+
filename="markup.pdf",
56+
markup={
57+
"html": """
58+
<div class="first">This document is created with HTML.</div>
59+
<br />
60+
<br />
61+
<br />
62+
<div>We can also define signing fields with text tags</div>
63+
<div>{{ signature : First signature : textTag : textTag }}</div>
64+
""",
65+
"css": """"body{ color: red; } div.first { color: blue; } """,
66+
},
67+
fields=[
68+
SignatureField(
69+
id="sign1",
70+
type="signature",
71+
page_num=0,
72+
# The position and size of the field. The coordinates provided here
73+
# (x=300, y=300) is the top-left of the rectangle.
74+
rect=dict(x=300, y=300, width=250, height=30),
75+
)
76+
],
77+
)
78+
79+
# Gather your signer data
80+
signer1 = EtchSigner(
81+
name="Jackie",
82+
83+
# Fields where the signer needs to sign.
84+
# Check your cast fields via the CLI (`anvil cast [cast_eid]`) or the
85+
# PDF Templates section on the Anvil app.
86+
# This basically says: "In the 'myNewFile' file (defined in
87+
# `file1` above), assign the signature field with cast id of
88+
# 'sign1' to this signer." You can add multiple signer fields here.
89+
fields=[
90+
SignerField(
91+
# this is the `id` in the `DocumentUpload` object above
92+
file_id="myNewFile",
93+
# This is the signing field id in the `SignatureField` above
94+
field_id="sign1",
95+
),
96+
SignerField(
97+
# this is the `id` in the `DocumentUpload` object above
98+
file_id="myNewFile",
99+
# This is the signing field id in the `SignatureField` above
100+
field_id="textTag",
101+
),
102+
],
103+
signer_type="embedded",
104+
#
105+
# You can also change how signatures will be collected.
106+
# "draw" will allow the signer to draw their signature
107+
# "text" will insert a text version of the signer's name into the
108+
# signature field.
109+
# signature_mode="draw",
110+
#
111+
# Whether or not to the signer is required to click each signature
112+
# field manually. If `False`, the PDF will be signed once the signer
113+
# accepts the PDF without making the user go through the PDF.
114+
# accept_each_field=False,
115+
#
116+
# URL of where the signer will be redirected after signing.
117+
# The URL will also have certain URL params added on, so the page
118+
# can be customized based on the signing action.
119+
# redirect_url="https://www.google.com",
120+
)
121+
122+
# Add your signer.
123+
packet.add_signer(signer1)
124+
125+
# Add files to your payload
126+
packet.add_file(file1)
127+
128+
# If needed, you can also override or add additional payload fields this way.
129+
# This is useful if the Anvil API has new features, but `python-anvil` has not
130+
# yet been updated to support it.
131+
# payload = packet.create_payload()
132+
# payload.aNewFeature = True
133+
134+
# Create your packet
135+
# If overriding/adding new fields, use the modified payload from
136+
# `packet.create_payload()`
137+
res = anvil.create_etch_packet(payload=packet, include_headers=True)
138+
print(res)
139+
140+
141+
if __name__ == '__main__':
142+
main()

examples/generate_pdf.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from python_anvil.api import Anvil
2+
from python_anvil.api_resources.payload import GeneratePDFPayload
3+
4+
5+
API_KEY = 'my-api-key'
6+
7+
8+
def main():
9+
anvil = Anvil(api_key=API_KEY)
10+
data = GeneratePDFPayload(
11+
type="html",
12+
title="Some Title",
13+
data=dict(
14+
html="<h2>HTML Heading</h2>",
15+
css="h2 { color: red }",
16+
),
17+
)
18+
response = anvil.generate_pdf(data)
19+
20+
# Write the bytes to disk
21+
with open('./generated.pdf', 'wb') as f:
22+
f.write(response)
23+
24+
25+
if __name__ == '__main__':
26+
main()

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 = "1.5.0"
4+
version = "1.6.0"
55
description = "Anvil API"
66

77
license = "MIT"

python_anvil/api.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def fill_pdf(
9393
**kwargs,
9494
)
9595

96-
def generate_pdf(self, payload: Union[AnyStr, Dict], **kwargs):
96+
def generate_pdf(self, payload: Union[AnyStr, Dict, GeneratePDFPayload], **kwargs):
9797
if not payload:
9898
raise ValueError("`payload` must be a valid JSON string or a dict")
9999

@@ -103,10 +103,12 @@ def generate_pdf(self, payload: Union[AnyStr, Dict], **kwargs):
103103
data = GeneratePDFPayload.parse_raw(
104104
payload, content_type="application/json"
105105
)
106+
elif isinstance(payload, GeneratePDFPayload):
107+
data = payload
106108
else:
107109
raise ValueError("`payload` must be a valid JSON string or a dict")
108110

109-
# Any data errors would come from here..
111+
# Any data errors would come from here
110112
api = RestRequest(client=self.client)
111113
return api.post(
112114
"generate-pdf", data=data.dict(by_alias=True, exclude_none=True), **kwargs

0 commit comments

Comments
 (0)