Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
- uses: pre-commit/[email protected]
# - uses: pre-commit/[email protected]
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,7 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

.direnv
.venv
result.pdf
4 changes: 0 additions & 4 deletions .markdownlint.json

This file was deleted.

15 changes: 0 additions & 15 deletions .pre-commit-config.yaml

This file was deleted.

1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
51 changes: 37 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
FROM python:3.11-buster
RUN apt update
RUN apt install cargo -y
RUN pip install --upgrade pip
# RUN groupadd -r typstuser && useradd -rm -g typstuser typstuser
# USER typstuser
# An example of using standalone Python builds with multistage images.

# First, build the application in the `/app` directory
FROM ghcr.io/astral-sh/uv:bookworm-slim AS builder
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy

# Configure the Python directory so it is consistent
ENV UV_PYTHON_INSTALL_DIR=/python

# Only use the managed Python version
ENV UV_PYTHON_PREFERENCE=only-managed

# Install Python before the project for caching
RUN uv python install 3.12

WORKDIR /app
COPY ./typst-http-api/requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY ./typst-http-api .
# equivalent to 'from hello import app'
RUN chmod +x gunicorn.sh
CMD ["sh", "gunicorn.sh"]
#Expose port 8000 of the container to the outside
EXPOSE 8000
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project --no-dev
ADD . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev

# Then, use a final image without uv
FROM debian:bookworm-slim

# Copy the Python version
COPY --from=builder --chown=python:python /python /python

# Copy the application from the builder
COPY --from=builder --chown=app:app /app /app

# Place executables in the environment at the front of the path
ENV PATH="/app/.venv/bin:$PATH"
WORKDIR /app
# Run the FastAPI application by default
CMD ["uvicorn", "src:app"]
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
run:
uv run fastapi dev typst-http-api/app.py
27 changes: 27 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
description = "";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/master";
};

outputs = {
self,
nixpkgs,
}: let
forAllSystems = function:
nixpkgs.lib.genAttrs [
"x86_64-darwin"
"x86_64-linux"
"aarch64-darwin"
"aarch64-linux"
] (system: function nixpkgs.legacyPackages.${system});
in {
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell {
# nativeBuildInputs is usually what you want -- tools you need to run
nativeBuildInputs = with pkgs; [
gnumake

uv
python3
ruff
pre-commit
];
};
});
};
}
17 changes: 17 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[project]
name = "typst-as-a-service"
version = "0.1.0"
description = "Typst as a service."
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi[standard]>=0.115.12",
"prometheus-fastapi-instrumentator>=7.1.0",
"pydantic>=2.11.2",
"slowapi>=0.1.9",
"typst>=0.13.2",
"uvicorn>=0.34.0",
]



1 change: 0 additions & 1 deletion requirements_dev.txt

This file was deleted.

Binary file added result.pdf
Binary file not shown.
54 changes: 54 additions & 0 deletions src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from __future__ import annotations

import logging

import typst
from fastapi import FastAPI, Request, Response, status
from fastapi.responses import StreamingResponse
from prometheus_fastapi_instrumentator import Instrumentator
from pydantic import BaseModel
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.util import get_remote_address

DEFAULT_CHUNK_SIZE = 1024

limiter = Limiter(key_func=get_remote_address)
app = FastAPI(docs_url=None, redoc_url=None)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)


Instrumentator().instrument(app).expose(app)

logger = logging.getLogger(__name__)


class CompilationError(BaseModel):
reason: str
content: str


@app.post("/")
@limiter.limit("6/minute")
async def build(request: Request, response: Response):
typst_bytes = await request.body()

try:
res = typst.compile(typst_bytes)
logger.info(f"successfully build {len(typst_bytes)}")
except RuntimeError as e:
response.status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
logger.error(f"failed to build {len(typst_bytes)}")
return CompilationError(reason="Compilation error", content=str(e))

def iterfile(
input_bytes: bytes,
chunk_size: int = DEFAULT_CHUNK_SIZE,
):
for i in range(0, len(input_bytes), chunk_size):
yield input_bytes[i : i + chunk_size]

return StreamingResponse(iterfile(res), media_type="application/pdf")


40 changes: 0 additions & 40 deletions tbump.toml

This file was deleted.

42 changes: 0 additions & 42 deletions typst-http-api/app.py

This file was deleted.

13 changes: 0 additions & 13 deletions typst-http-api/gunicorn.conf.py

This file was deleted.

2 changes: 0 additions & 2 deletions typst-http-api/gunicorn.sh

This file was deleted.

3 changes: 0 additions & 3 deletions typst-http-api/requirements.txt

This file was deleted.

Loading