Skip to content

Commit 9643f8d

Browse files
Init app-catala from Strata Catala rules engine (#263)
Changes Adds the Catala rules engine as another app to test. I ran nava-platform app install --template-uri https://github.com/navapbc/strata-template-rules-engine-catala . app-catala I'll add the infra portion in a separate PR. Testing Just an init.
1 parent 389e912 commit 9643f8d

40 files changed

+3171
-1
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: CI - app-catala
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- app-catala/**
9+
- .github/workflows/ci-app-catala.yml
10+
pull_request:
11+
paths:
12+
- app-catala/**
13+
- .github/workflows/ci-app-catala.yml
14+
15+
defaults:
16+
run:
17+
working-directory: ./app-catala
18+
19+
jobs:
20+
lint:
21+
name: Lint
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
- name: Run format check
27+
run: make format-check
28+
29+
- name: Run linting
30+
run: make lint
31+
32+
test:
33+
name: Test
34+
runs-on: ubuntu-latest
35+
steps:
36+
- uses: actions/checkout@v4
37+
38+
- name: Build
39+
run: make build
40+
41+
- name: Run Python tests
42+
run: make test-coverage
43+
44+
catala-test:
45+
name: Catala Tests
46+
runs-on: ubuntu-latest
47+
steps:
48+
- uses: actions/checkout@v4
49+
50+
- name: Run Catala tests
51+
run: |
52+
docker run --rm \
53+
-v "$GITHUB_WORKSPACE/app-catala":/work \
54+
-w /work \
55+
-e HOME=/home/ocaml \
56+
--user root \
57+
registry.gitlab.inria.fr/catala/ci-images:latest-python \
58+
sh -c 'eval $(opam env) && make SHELL=/bin/sh catala-test'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
2+
_commit: 4f60dcd
3+
_src_path: https://github.com/navapbc/strata-template-rules-engine-catala
4+
app_local_port: 3001
5+
app_name: app-catala

app-catala/.gitignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Python compiled/optimized files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# Python packaging stuff
7+
dist/
8+
*.egg-info
9+
10+
# Python testing stuff
11+
.coverage*
12+
coverage.*
13+
.testmondata
14+
.pytest_cache/
15+
16+
# Python virtual environments
17+
.venv
18+
19+
# Environment variables
20+
.env
21+
.envrc
22+
override.env
23+
24+
# mypy
25+
.mypy_cache
26+
27+
# VSCode Workspace
28+
*.code-workspace
29+
.vscode
30+
31+
# Poetry installer local error logs
32+
poetry-installer-error-*.log
33+
34+
# Catala build artifacts
35+
_build/
36+
_target/

app-catala/Dockerfile

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Multi-stage Dockerfile for Catala rules engine
2+
#
3+
# Stages:
4+
# dev - Development environment with all tools
5+
# release - Production-ready image
6+
7+
# -------------------
8+
# Base Python image
9+
# -------------------
10+
FROM python:3.13-slim AS base
11+
12+
RUN pip install --no-cache-dir poetry==2.1.1
13+
14+
RUN apt-get update \
15+
&& apt-get upgrade --yes \
16+
&& apt-get install --no-install-recommends --yes \
17+
build-essential \
18+
wget \
19+
libgmp-dev libmpfr-dev libmpc-dev gcc libc6-dev \
20+
&& rm -fr /var/lib/apt/lists/*
21+
22+
ARG RUN_UID
23+
ARG RUN_USER
24+
25+
RUN : "${RUN_USER:?RUN_USER and RUN_UID need to be set and non-empty.}" && \
26+
[ "${RUN_USER}" = "root" ] || \
27+
(useradd --create-home --create --user-group --home "/home/${RUN_USER}" --uid ${RUN_UID} "${RUN_USER}" \
28+
&& mkdir /app \
29+
&& chown -R ${RUN_UID} "/home/${RUN_USER}" /app)
30+
31+
# -----------
32+
# Dev image
33+
# -----------
34+
FROM base AS dev
35+
ARG RUN_USER
36+
USER ${RUN_USER}
37+
WORKDIR /app
38+
39+
COPY pyproject.toml poetry.lock* ./
40+
41+
ENV POETRY_VIRTUALENVS_IN_PROJECT=true
42+
43+
RUN poetry install --no-root --extras dev
44+
45+
ENV HOST=0.0.0.0
46+
47+
# Add project's virtual env to the PATH so we can directly run scripts defined in pyproject.toml
48+
ENV PATH="/app/.venv/bin:$PATH"
49+
50+
CMD ["python", "-m", "src.main"]
51+
52+
# ---------
53+
# Release
54+
# ---------
55+
FROM base AS release
56+
ARG RUN_USER
57+
58+
WORKDIR /app
59+
COPY . /app
60+
61+
RUN rm -fr /app/.venv
62+
63+
ENV POETRY_VIRTUALENVS_IN_PROJECT=true
64+
65+
# Install production dependencies and project (for console scripts like start-server)
66+
RUN poetry install --only main
67+
68+
# Add project's virtual env to the PATH so we can directly run scripts defined in pyproject.toml
69+
ENV PATH="/app/.venv/bin:$PATH"
70+
ENV HOST=0.0.0.0
71+
72+
USER ${RUN_USER}
73+
74+
CMD ["start-server"]

app-catala/Makefile

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
##################################################
2+
# Constants
3+
##################################################
4+
5+
APP_NAME := app-catala
6+
7+
SHELL = /bin/bash -o pipefail
8+
9+
APP_DIR := app
10+
ifdef CI
11+
DOCKER_EXEC_ARGS := -T -e CI -e PYTEST_ADDOPTS="--color=yes"
12+
MYPY_FLAGS := --no-pretty
13+
MYPY_POSTPROC := | perl -pe "s/^(.+):(\d+):(\d+): error: (.*)/::warning file=$(APP_DIR)\/\1,line=\2,col=\3::\4/"
14+
endif
15+
16+
ifeq "$(PY_RUN_APPROACH)" "local"
17+
PY_RUN_CMD := poetry run
18+
else
19+
PY_RUN_CMD := docker compose run $(DOCKER_EXEC_ARGS) --rm $(APP_NAME) poetry run
20+
endif
21+
22+
# Docker user configuration
23+
ifeq ($(user),)
24+
RUN_USER ?= $(or $(strip $(USER)),nodummy)
25+
RUN_UID ?= $(or $(strip $(shell id -u)),4000)
26+
else
27+
RUN_USER = $(user)
28+
RUN_UID = $(or $(strip $(uid)),0)
29+
endif
30+
31+
export RUN_USER
32+
export RUN_UID
33+
34+
release-build:
35+
docker buildx build \
36+
--target release \
37+
--platform=linux/amd64 \
38+
--build-arg RUN_USER=$(RUN_USER) \
39+
--build-arg RUN_UID=$(RUN_UID) \
40+
$(OPTS) \
41+
.
42+
43+
##################################################
44+
# Local Development Environment Setup
45+
##################################################
46+
47+
setup-local:
48+
poetry config virtualenvs.in-project true
49+
poetry install --no-root
50+
51+
##################################################
52+
# Build & Run
53+
##################################################
54+
55+
build:
56+
docker compose build
57+
58+
start:
59+
docker compose up --detach
60+
61+
run-logs: start
62+
docker compose logs --follow --no-color $(APP_NAME)
63+
64+
init: build
65+
66+
stop:
67+
docker compose down
68+
69+
check: format-check lint test
70+
71+
##################################################
72+
# Catala Compilation
73+
##################################################
74+
75+
CATALA_DIR := catala
76+
CATALA_GEN_DIR := src/generated
77+
78+
catala-build: ## Compile Catala sources to Python via clerk
79+
cd $(CATALA_DIR) && clerk build
80+
# There's a bug where dates.py is missing from the target code
81+
# https://github.com/CatalaLang/catala/issues/981
82+
cp $(CATALA_DIR)/_build/libcatala/python/dates.py $(CATALA_GEN_DIR)/
83+
cp $(CATALA_DIR)/_target/paidleave/python/* $(CATALA_GEN_DIR)/
84+
85+
catala-test: ## Run Catala test assertions via clerk
86+
cd $(CATALA_DIR) && clerk test
87+
88+
catala-ci: ## Run Catala CI build and tests
89+
cd $(CATALA_DIR) && clerk ci
90+
91+
##################################################
92+
# Testing
93+
##################################################
94+
95+
test: ## Run Python tests
96+
$(PY_RUN_CMD) pytest $(args)
97+
98+
test-watch: ## Run tests continually and watch for changes
99+
$(PY_RUN_CMD) pytest-watch --clear $(args)
100+
101+
test-coverage: ## Run tests and generate coverage report
102+
$(PY_RUN_CMD) coverage run --branch --source=src -m pytest $(args)
103+
$(PY_RUN_CMD) coverage report
104+
105+
test-coverage-report: ## Open HTML test coverage report
106+
$(PY_RUN_CMD) coverage html --directory .coverage_report
107+
open .coverage_report/index.html
108+
109+
test-all: catala-test test ## Run all Catala and Python tests
110+
111+
##################################################
112+
# Formatting and linting
113+
##################################################
114+
115+
format: ## Format Python files
116+
$(PY_RUN_CMD) ruff format src tests
117+
118+
format-check: ## Check Python file formatting
119+
$(PY_RUN_CMD) ruff format --check src tests
120+
121+
lint: lint-ruff lint-mypy ## Lint all files
122+
123+
lint-ruff:
124+
$(PY_RUN_CMD) ruff check src tests
125+
126+
lint-mypy:
127+
$(PY_RUN_CMD) mypy --show-error-codes $(MYPY_FLAGS) src $(MYPY_POSTPROC)
128+
129+
##################################################
130+
# Miscellaneous Utilities
131+
##################################################
132+
133+
login: start ## Start shell in running container
134+
docker exec -it $(APP_NAME) bash
135+
136+
help: ## Prints the help documentation and info about each command
137+
@grep -E '^[/a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

app-catala/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# app-catala Rules Engine
2+
3+
## Introduction
4+
5+
This is a rules engine built with [Catala](https://catala-lang.org/), a domain-specific language for turning legislative texts into executable code. Rules are encoded in `.catala_en` files and compiled to Python, then exposed via a FastAPI-based REST API.
6+
7+
## Project Directory Structure
8+
9+
```text
10+
root
11+
├── app-catala
12+
│ └── catala Catala source files encoding legislative rules
13+
│ └── tests Catala test assertions
14+
│ └── src
15+
│ └── api.py FastAPI application exposing rules as endpoints
16+
│ └── generated Python code generated by the Catala compiler
17+
│ └── tests Python tests for the API layer
18+
│ └── local.env Environment variable configuration for local development
19+
│ └── Makefile Frequently used CLI commands
20+
│ └── pyproject.toml Python project configuration file
21+
│ └── Dockerfile Docker build file
22+
└── └── docker-compose.yml Docker Compose config for local development
23+
```
24+
25+
## Key Commands
26+
27+
| Command | Description |
28+
|---|---|
29+
| `make catala-build` | Compile Catala sources to Python via clerk and copy `dates.py` to `src/generated/` |
30+
| `make catala-test` | Run Catala test assertions via clerk |
31+
| `make test` | Run Python API tests |
32+
| `make test-all` | Run all Catala and Python tests |
33+
| `make format` | Format Python files |
34+
| `make lint` | Lint Python files |
35+
| `make check` | Run format check, lint, and tests |
36+
37+
See the [Makefile](/app-catala/Makefile) for a full list of commands.
38+
39+
## Docker and Native Development
40+
41+
The dev Docker image installs Python dependencies at build time and volume-mounts `src/`, `tests/`, and `catala/` for live reloading during development. Catala compilation (via `clerk`) should be run on the host or in a separate container with the Catala compiler installed.
42+
43+
Tests, linting, and formatting can be run either inside Docker or natively.
44+
45+
* `export PY_RUN_APPROACH=local` will run commands natively
46+
* `export PY_RUN_APPROACH=docker` will run commands within Docker (default)
47+
48+
## Adding New Rules
49+
50+
1. Create a new `.catala_en` file in `catala/src/` encoding your legislative text and rules.
51+
2. Add a target entry in `catala/clerk.toml` for the new module.
52+
3. Add test assertions in `catala/tests/`.
53+
4. Run `make catala-build` to compile to Python (typechecking happens as part of the build; outputs go to `src/generated/`).
54+
5. Import the generated module in `src/api.py` and create an endpoint.
55+
6. Add Python tests in `tests/`.

app-catala/catala/clerk.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[project]
2+
include_dirs = ["src"]
3+
build_dir = "_build"
4+
target_dir = "_target"
5+
6+
[[target]]
7+
name = "paidleave"
8+
modules = ["Paidleave"]
9+
tests = ["tests/paidleave_test.catala_en"]
10+
backends = ["python"]

0 commit comments

Comments
 (0)