Skip to content

Commit 9733a4e

Browse files
committed
Publish python example
0 parents  commit 9733a4e

24 files changed

Lines changed: 1631 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
workflow_dispatch:
9+
10+
jobs:
11+
lint-format-test:
12+
name: Format, linting, type checking, and tests
13+
runs-on: ubuntu-latest
14+
timeout-minutes: 30
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Set up Python
19+
uses: actions/setup-python@v5
20+
with:
21+
python-version: '3.12'
22+
23+
- name: Install uv
24+
uses: astral-sh/setup-uv@v4
25+
with:
26+
enable-cache: true
27+
28+
- name: Install dependencies
29+
run: uv sync --all-extras
30+
31+
- name: Check formatting with ruff
32+
run: uv run ruff format --check .
33+
34+
- name: Run ruff linting
35+
run: uv run ruff check .
36+
37+
- name: Run type checking with mypy
38+
run: uv run mypy .
39+
40+
- name: Run unit tests
41+
run: uv run pytest tests/ -v --tb=short
42+
43+
spelling:
44+
name: Spell Check with Typos
45+
runs-on: ubuntu-latest
46+
steps:
47+
- name: Checkout Actions Repository
48+
uses: actions/checkout@v4
49+
- name: Spell Check Repo
50+
uses: crate-ci/typos@v1.35.5
51+
52+
validate-links:
53+
name: Check for dead links
54+
runs-on: ubuntu-latest
55+
steps:
56+
- uses: actions/checkout@v4
57+
- name: check readme links
58+
run: npx linkinator README.md

.github/workflows/e2e.yml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: E2E Tests
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
workflow_dispatch:
8+
9+
jobs:
10+
e2e-tests:
11+
name: E2E Tests with Bruno
12+
runs-on: ubuntu-latest
13+
timeout-minutes: 30
14+
15+
steps:
16+
- name: Checkout example service
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: '3.12'
23+
24+
- name: Install uv
25+
uses: astral-sh/setup-uv@v4
26+
with:
27+
enable-cache: true
28+
29+
- name: Install dependencies
30+
run: uv sync
31+
32+
- name: Start service in background
33+
run: |
34+
uv run uvicorn main:app --host 127.0.0.1 --port 3003 &
35+
echo $! > service.pid
36+
echo "Service started with PID $(cat service.pid)"
37+
38+
- name: Checkout oicana repository (for Bruno tests)
39+
uses: actions/checkout@v4
40+
with:
41+
repository: oicana/oicana
42+
path: oicana
43+
44+
- name: Setup Node.js for Bruno CLI
45+
uses: actions/setup-node@v4
46+
with:
47+
node-version: '20'
48+
49+
- name: Install Bruno CLI
50+
run: npm install -g @usebruno/cli
51+
52+
- name: Wait for service to be ready
53+
run: |
54+
echo "Waiting for service on port 3003..."
55+
timeout=60
56+
elapsed=0
57+
while ! curl -f http://localhost:3003/templates 2>/dev/null; do
58+
if [ $elapsed -ge $timeout ]; then
59+
echo "Service did not start within ${timeout} seconds"
60+
if [ -f service.pid ]; then
61+
echo "Service PID: $(cat service.pid)"
62+
ps aux | grep $(cat service.pid) || echo "Service process not found"
63+
fi
64+
exit 1
65+
fi
66+
echo "Waiting... (${elapsed}s/${timeout}s)"
67+
sleep 2
68+
elapsed=$((elapsed + 2))
69+
done
70+
echo "Service is ready!"
71+
72+
- name: Run Bruno E2E tests
73+
working-directory: oicana/e2e-tests/bruno
74+
run: bru run --env fastapi --reporter-html results.html
75+
76+
- name: Upload test results
77+
if: always()
78+
uses: actions/upload-artifact@v4
79+
with:
80+
name: test-results
81+
path: oicana/e2e-tests/bruno/results.html
82+
retention-days: 30
83+
84+
- name: Stop service
85+
if: always()
86+
run: |
87+
if [ -f service.pid ]; then
88+
PID=$(cat service.pid)
89+
echo "Stopping service with PID $PID"
90+
kill $PID || true
91+
# Wait for graceful shutdown
92+
sleep 5
93+
# Force kill if still running
94+
kill -9 $PID 2>/dev/null || true
95+
fi

.gitignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
blobs/*
2+
!blobs/00000000-0000-0000-0000-000000000000
3+
4+
__pycache__/
5+
*.py[cod]
6+
*$py.class
7+
*.so
8+
.Python
9+
build/
10+
develop-eggs/
11+
dist/
12+
downloads/
13+
eggs/
14+
.eggs/
15+
lib/
16+
lib64/
17+
parts/
18+
sdist/
19+
var/
20+
wheels/
21+
*.egg-info/
22+
.installed.cfg
23+
*.egg
24+
MANIFEST
25+
.pytest_cache/
26+
.coverage
27+
htmlcov/
28+
.tox/
29+
.venv
30+
venv/
31+
ENV/
32+
env/
33+
.uv/
34+
.ruff_cache/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Niklas Eicker
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Oicana Example FastAPI
2+
3+
Small example Python web service with [FastAPI](https://fastapi.tiangolo.com/) that uses Oicana for PDF templating.
4+
5+
## Getting Started
6+
7+
1. Install dependencies: `uv sync`
8+
2. Start the service: `uv run python main.py` or `uv run uvicorn main:app --host 127.0.0.1 --port 3003`
9+
3. Visit http://127.0.0.1:3003/docs for the Swagger documentation
10+
11+
## Licensing
12+
13+
The code of this example project is licensed under the [MIT license](LICENSE).
14+
15+
But please be aware that the dependency `oicana` [is licensed under PolyForm Noncommercial License 1.0.0][oicana-license].
16+
17+
18+
[oicana-license]: https://github.com/oicana/oicana?tab=readme-ov-file#licensing
35 KB
Binary file not shown.

main.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import logging
2+
from contextlib import asynccontextmanager
3+
4+
from fastapi import FastAPI
5+
from fastapi.middleware.gzip import GZipMiddleware
6+
from fastapi.responses import HTMLResponse
7+
8+
from routers import blobs, certificates, templates
9+
10+
logging.basicConfig(
11+
level=logging.INFO,
12+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
13+
)
14+
logger = logging.getLogger(__name__)
15+
16+
17+
@asynccontextmanager
18+
async def lifespan(app: FastAPI):
19+
logger.info("Warming up templates...")
20+
templates.warm_up_templates()
21+
logger.info("Templates warmed up")
22+
yield
23+
logger.info("Server shutting down")
24+
25+
26+
app = FastAPI(
27+
title="Oicana example",
28+
description="Python FastAPI example with Oicana",
29+
version="1.0",
30+
lifespan=lifespan,
31+
)
32+
33+
app.add_middleware(GZipMiddleware, minimum_size=1000)
34+
35+
app.include_router(templates.router, prefix="/templates", tags=["template"])
36+
app.include_router(certificates.router, prefix="/certificates", tags=["certificates"])
37+
app.include_router(blobs.router, prefix="/blobs", tags=["blob"])
38+
39+
40+
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
41+
async def root():
42+
return 'Visit the swagger documentation at <a href="/docs">/docs</a>'
43+
44+
45+
if __name__ == "__main__":
46+
import uvicorn
47+
48+
uvicorn.run(app, host="127.0.0.1", port=3003)

pyproject.toml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[project]
2+
name = "oicana-example-fastapi"
3+
version = "0.1.0"
4+
description = "Example application using Oicana Python integration for PDF templating"
5+
readme = "README.md"
6+
requires-python = ">=3.11"
7+
license = { text = "MIT" }
8+
dependencies = [
9+
"fastapi>=0.115.0",
10+
"uvicorn[standard]>=0.32.0",
11+
"oicana>=0.1.0a2",
12+
"python-multipart>=0.0.12",
13+
]
14+
15+
[dependency-groups]
16+
dev = [
17+
"pytest>=8.3.0",
18+
"httpx>=0.28.0",
19+
"ruff>=0.8.0",
20+
"mypy>=1.13.0",
21+
]
22+
23+
[tool.ruff]
24+
line-length = 100
25+
target-version = "py311"
26+
27+
[tool.ruff.lint]
28+
select = ["E", "F", "I", "UP", "B", "SIM", "PL"]
29+
ignore = ["PLR2004", "PLR0913", "B008"]
30+
31+
[tool.mypy]
32+
python_version = "3.11"
33+
warn_return_any = true
34+
warn_unused_configs = true
35+
disallow_untyped_defs = false
36+
check_untyped_defs = true
37+
ignore_missing_imports = true
38+
39+
[tool.pytest.ini_options]
40+
testpaths = ["tests"]
41+
python_files = ["test_*.py"]
42+
python_classes = ["Test*"]
43+
python_functions = ["test_*"]

routers/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)