Skip to content

Commit 1f7764e

Browse files
committed
feat: add pdf_from_html_without_s3 mode to generate pdfs without an s3 bucket
1 parent 95a1706 commit 1f7764e

File tree

3 files changed

+78
-7
lines changed

3 files changed

+78
-7
lines changed

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "uptick-splat"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
description = "Django library for invoking splat"
55
authors = ["william chu <[email protected]>"]
66
readme = "README.md"

uptick_splat/__init__.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
from .config import config, configure # noqa
2-
from .utils import SplatPDFGenerationFailure, pdf_with_splat # noqa
1+
from .config import config, configure_splat
2+
from .utils import SplatPDFGenerationFailure, pdf_from_html, pdf_from_html_without_s3
33

44
__all__ = [
55
"config",
6-
"configure",
6+
"configure_splat",
77
"SplatPDFGenerationFailure",
8-
"pdf_with_splat",
8+
"pdf_from_html",
9+
"pdf_from_html_without_s3",
910
"__version__",
1011
]

uptick_splat/utils.py

+72-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import base64
12
import json
23
import os
34
import re
@@ -18,14 +19,23 @@ def strip_dangerous_s3_chars(filename: str) -> str:
1819
return re.sub(r"[^0-9a-zA-Z_\.\-\s]", "", filename)
1920

2021

21-
def pdf_with_splat(
22+
def pdf_from_html(
2223
body_html: str,
2324
*,
2425
bucket_name: Optional[str] = None,
2526
s3_filepath: Optional[str] = None,
27+
javascript: bool = False,
2628
fields: Optional[Dict] = None,
2729
conditions: Optional[List[List]] = None,
2830
) -> Optional[bytes]:
31+
"""Generates a pdf from html using the splat lambda function.
32+
33+
:param body_html: the html to convert to pdf
34+
:param bucket_name: the bucket to upload the html to. defaults to config.default_bucket_name
35+
:param s3_filepath: the path to upload the pdf to. defaults to a random path in the bucket
36+
:param fields: additional fields to add to the presigned url
37+
:param conditions: additional conditions to add to the presigned url
38+
"""
2939
bucket_name = bucket_name or config.default_bucket_name
3040
if not bucket_name:
3141
raise SplatPDFGenerationFailure(
@@ -74,7 +84,11 @@ def pdf_with_splat(
7484
)
7585

7686
splat_body = json.dumps(
77-
{"document_url": document_url, "presigned_url": presigned_url}
87+
{
88+
"document_url": document_url,
89+
"presigned_url": presigned_url,
90+
"javascript": javascript,
91+
}
7892
)
7993

8094
response = lambda_client.invoke(
@@ -127,3 +141,59 @@ def pdf_with_splat(
127141
except (KeyError, JSONDecodeError):
128142
splat_error = splat_response
129143
raise SplatPDFGenerationFailure(f"Error returned from splat: {splat_error}")
144+
145+
146+
def pdf_from_html_without_s3(
147+
body_html: str,
148+
javascript: bool = False,
149+
) -> Optional[bytes]:
150+
"""Generates a pdf from html without using s3. This is useful for small pdfs and html documents.
151+
152+
The maximum size of the html document is 6MB. The maximum size of the pdf is 6MB.
153+
"""
154+
session = config.get_session_fn()
155+
lambda_client = session.client(
156+
"lambda",
157+
region_name=config.function_region,
158+
config=Config(read_timeout=60 * 15, retries={"max_attempts": 0}),
159+
)
160+
161+
splat_body = json.dumps({"document_content": body_html, "javascript": javascript})
162+
163+
response = lambda_client.invoke(
164+
FunctionName=config.function_name,
165+
Payload=json.dumps({"body": splat_body}),
166+
)
167+
168+
# Check response of the invocation. Note that a successful invocation doesn't mean the PDF was generated.
169+
if response.get("StatusCode") != 200:
170+
raise SplatPDFGenerationFailure(
171+
"Invalid response while invoking splat lambda -"
172+
f" {response.get('StatusCode')}"
173+
)
174+
175+
# Parse lambda response
176+
try:
177+
splat_response = json.loads(response["Payload"].read().decode("utf-8"))
178+
except (KeyError, AttributeError):
179+
raise SplatPDFGenerationFailure("Invalid lambda response format")
180+
except JSONDecodeError:
181+
raise SplatPDFGenerationFailure("Error decoding splat response body as json")
182+
183+
# ==== Success ====
184+
if splat_response.get("statusCode") == 200:
185+
return base64.b64decode(splat_response.get("body"))
186+
# ==== Failure ====
187+
# Lambda timeout et al.
188+
elif error_message := splat_response.get("errorMessage"):
189+
raise SplatPDFGenerationFailure(
190+
f"Error returned from lambda invocation: {error_message}"
191+
)
192+
# All other errors
193+
else:
194+
# Try to extract an error message from splat response
195+
try:
196+
splat_error = json.loads(splat_response["body"])["errors"][0]
197+
except (KeyError, JSONDecodeError):
198+
splat_error = splat_response
199+
raise SplatPDFGenerationFailure(f"Error returned from splat: {splat_error}")

0 commit comments

Comments
 (0)