Python using FastAPI
In this chapter, you’ll integrate Oicana into a Python web service using FastAPI. FastAPI is a modern, high-performance web framework for building APIs with Python based on standard Python type hints. We’ll create a simple web service that compiles your Oicana template to PDF and serves it via an HTTP endpoint.
Note
This chapter assumes that you have a working Python 3.9+ setup with pip or uv. If that is not the case, please follow the official Python installation guide to install Python on your machine.
Let’s start with a fresh FastAPI project. First, create a new directory for your project, then set up a virtual environment and install FastAPI with pip install fastapi uvicornpip install fastapi uvicorn (or uv add fastapi uvicornuv add fastapi uvicorn if using uv). Create a main.pymain.py file with the following basic FastAPI application:
from fastapi import FastAPIapp = FastAPI()@app.get("/")async def root(): return {"message": "Hello World"}
from fastapi import FastAPIapp = FastAPI()@app.get("/")async def root(): return {"message": "Hello World"}
You can test it by running uvicorn main:app --reloaduvicorn main:app --reload (or fastapi dev main.pyfastapi dev main.py if using FastAPI CLI) and navigating to http://localhost:8000 in your browser.
We will define a new endpoint to compile our Oicana template to a PDF and return the PDF file to the user.
- Create a new directory in the Python project called
templatestemplatesand copyexample-0.1.0.zipexample-0.1.0.zipinto that directory. - Add the
oicanaoicanaPyPI package as a dependency withpip install oicanapip install oicana(oruv add oicanauv add oicana). -
Update
main.pymain.pyto load the template at startup and add a compile endpoint:from contextlib import asynccontextmanagerfrom pathlib import Pathfrom fastapi import FastAPIfrom fastapi.responses import Responsefrom oicana import Template, CompilationModetemplate: Template@asynccontextmanagerasync def lifespan(app: FastAPI):global templatetemplate_path = Path("templates/example-0.1.0.zip")template_bytes = template_path.read_bytes()# Template registration uses development mode by defaulttemplate = Template(template_bytes)yieldapp = FastAPI(lifespan=lifespan)@app.post("/compile")async def compile_template():pdf = template.compile(mode=CompilationMode.DEVELOPMENT)return Response(content=bytes(pdf),media_type="application/pdf",headers={"Content-Disposition": "attachment; filename=example.pdf"},)from contextlib import asynccontextmanagerfrom pathlib import Pathfrom fastapi import FastAPIfrom fastapi.responses import Responsefrom oicana import Template, CompilationModetemplate: Template@asynccontextmanagerasync def lifespan(app: FastAPI):global templatetemplate_path = Path("templates/example-0.1.0.zip")template_bytes = template_path.read_bytes()# Template registration uses development mode by defaulttemplate = Template(template_bytes)yieldapp = FastAPI(lifespan=lifespan)@app.post("/compile")async def compile_template():pdf = template.compile(mode=CompilationMode.DEVELOPMENT)return Response(content=bytes(pdf),media_type="application/pdf",headers={"Content-Disposition": "attachment; filename=example.pdf"},)
This code loads the template once at application startup using FastAPI’s lifespan context manager. The/compile/compileendpoint compiles the template and returns the PDF file. We explicitly pass the compilation mode withtemplate.compile(mode=CompilationMode.DEVELOPMENT)template.compile(mode=CompilationMode.DEVELOPMENT)so the template uses the development value you defined for theinfoinfoinput ({ "name": "Chuck Norris" }{ "name": "Chuck Norris" }). In a follow-up step, we will set an input value instead.
After restarting the service, you can test the endpoint with curl:
curl -X POST http://localhost:8000/compile --output example.pdf
curl -X POST http://localhost:8000/compile --output example.pdf
The generated example.pdfexample.pdf file should contain your template with the development value.
You can also explore the automatically generated API documentation by navigating to http://localhost:8000/docs in your browser. FastAPI provides interactive API documentation out of the box.
The PDF generation should not take longer than a couple of milliseconds.
For better performance in production environments with heavy load, consider using FastAPI’s support for async operations and deploying with multiple worker processes using Gunicorn or similar ASGI servers.
Our compile_templatecompile_template function is currently calling template.compile()template.compile() with development mode. Now we’ll provide explicit input values and switch to production mode:
import json@app.post("/compile")async def compile_template(): pdf = template.compile( json_inputs={"info": json.dumps({"name": "Baby Yoda"})} ) return Response( content=bytes(pdf), media_type="application/pdf", headers={ "Content-Disposition": "attachment; filename=example.pdf" }, )
import json@app.post("/compile")async def compile_template(): pdf = template.compile( json_inputs={"info": json.dumps({"name": "Baby Yoda"})} ) return Response( content=bytes(pdf), media_type="application/pdf", headers={ "Content-Disposition": "attachment; filename=example.pdf" }, )
Notice that we removed the explicit mode=CompilationMode.DEVELOPMENTmode=CompilationMode.DEVELOPMENT parameter. The compile()compile() method defaults to CompilationMode.PRODUCTIONCompilationMode.PRODUCTION when no mode is specified. Production mode is the recommended default for all document compilation in your application - it ensures you never accidentally generate a document with test data. In production mode, the template will never fall back to development values. If an input value is missing in production mode and the input does not have a default value, the compilation will fail unless your template handles nonenone values for that input.
Calling the endpoint now will result in a PDF with “Baby Yoda” instead of “Chuck Norris”. Building on this minimal service, you could set input values based on database entries or the request payload. Take a look at the open source FastAPI example project on GitHub for a more complete showcase of the Oicana Python integration, including blob inputs, error handling, and request models.