Skip to content

Instantly share code, notes, and snippets.

@matthewpoer
Last active February 3, 2023 12:39
Show Gist options
  • Save matthewpoer/626d967eb276773a8ac69fa6498b8667 to your computer and use it in GitHub Desktop.
Save matthewpoer/626d967eb276773a8ac69fa6498b8667 to your computer and use it in GitHub Desktop.

Using Weasyprint with AWS Lambda on Python 3.8

A tedious because Lambda's Python 3.8 runtime does not include all of the typical build tools to generate Weasyprint's dependencies.

Also included here are steps to generate fonts, because Lambda doesn't have any and while it is interesting to look at a fully formatted document where all of the characters are .notdef glyphs, it's more productive to be able to read your pretty new PDF file.

The files in this gist are "flat" because gist does not allow us to show directories, so note the following dir structure:

serverless.yml
layers/
    fonts/
        setup.sh
        README.md
    weasyprint_deps/
        setup.sh
        Dockerfile
        README.md

Run the setup.sh and commit the zipballs

While it is not generally good practice to track binary files in git, I'm doing it anyway because they're small and stable. You will rarely if ever want to regenerate them and you'll save a lot of time to just track them and let them run through your CICD pipeline. Use LFS.

The following will create your zipball files:

cd layers/fonts/ && ./setup.sh \
cd ../weasyprint_deps/ && ./setup.sh

The zipballs are referenced directly in your serverless.yml to be used as layers on your Lambda function.

Fonts Lambda Layer

Lambda doesn't come with any fonts. This script downloads Open Sans and packages it for use as a Lambda layer.

./setup.sh
#!/usr/bin/env bash
mkdir -p fonts
curl "https://fonts.google.com/download?family=Open%20Sans" --silent -o fonts/OpenSans.zip
unzip -q fonts/OpenSans.zip -d fonts/
rm fonts/OpenSans.zip
cat <<EOT > fonts/fonts.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<dir>/opt/fonts</dir>
<cachedir>/tmp/fonts-cache/</cachedir>
<config></config>
</fontconfig>
EOT
zip -r fonts.zip .
rm -rf fonts
---
custom:
pythonRequirements:
dockerizePip: non-linux
useDownloadCache: true
useStaticCache: true
layer: true
layers:
fonts:
compatibleRuntimes: [python3.8]
description: Installs Open Sans font
name: ${self:service}-fonts
package:
artifact: layers/fonts/fonts.zip
retain: false
weasyprint_deps:
compatibleRuntimes: [python3.8]
description: Installs weasyprint dependencies
name: ${self:service}-weasyprint_deps
package:
artifact: layers/weasyprint_deps/weasyprint_deps.zip
retain: false
functions:
main:
handler: handler.lambda_main
layers:
- {Ref: FontsLambdaLayer}
- {Ref: PythonRequirementsLambdaLayer}
- {Ref: WeasyprintUnderscoredepsLambdaLayer}
plugins:
- serverless-python-requirements
provider:
environment:
FONTCONFIG_PATH: /opt/fonts
STAGE: ${self:provider.stage}
name: aws
region: us-west-2
runtime: python3.8
stage: ${opt:stage, 'dev'}
timeout: 60
service: some-service-name
FROM lambci/lambda:build-python3.8
WORKDIR /tmp
RUN yum install -y \
rpmdevtools \
yum-utils
RUN yumdownloader \
binutils \
cairo \
expat \
fontconfig \
freetype \
glib2 \
libglvnd \
libglvnd-egl \
libglvnd-glx \
libpng \
libuuid \
libX11 \
libXau \
libxcb \
libXext \
libXrender \
pango \
pixman \
libthai \
fribidi \
harfbuzz \
graphite2 \
&& rpmdev-extract *rpm
WORKDIR /
RUN mkdir -p /opt/bin \
&& mkdir -p /opt/lib \
&& rm -rf /opt/lib/*
RUN cp -P /tmp/*/usr/bin/ld.bfd /opt/bin/ld \
&& cp -P /tmp/*/usr/bin/objdump /opt/bin/objdump \
&& cp -P -R /tmp/*/usr/lib64/* /opt/lib \
&& ln /opt/lib/libcairo.so.2 /opt/lib/libcairo \
&& ln /opt/lib/libpango-1.0.so.0 /opt/lib/libpango-1.0 \
&& ln /opt/lib/libpangocairo-1.0.so.0 /opt/lib/pangocairo-1.0 \
&& cd opt \
&& zip -r weasyprint_deps.zip .

Weasyprint Dependencies Lambda Layer

Lambda lacks dependencies required by the Weasyprint library. To resolve this, we inject a Lambda layer with the missing libraries. We do this by building a Docker image based on an environment very similar to Lambda, using it to find and bundle the missing libraries, and exporting to this directory. The result is the binary zipball weasyprint_lambda_layer.zip which we track in VCS for the convince of not having CICD regenerate it every time (i.e. we should not need to regenerate this with any frequency).

The missing libraries are cairo and pango and the binary executables ld and objdump. Other packages are included because they are dependencies of these.

Generate the package with ./setup.sh

This process was created after reviewing the following:

#!/usr/bin/env bash
docker build -t weasyprint . && \
docker create --name weasyprint weasyprint . && \
docker cp weasyprint:/opt/weasyprint_deps.zip . && \
docker rm weasyprint
@tt-kianhean
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment