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
81 changes: 71 additions & 10 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ parameters:
publish-branch:
type: string
default: "main"
docker-registry-host:
type: string
default: "registry.gitlab.com"
docker-repo-tabular:
type: string
default: "registry.gitlab.com/etalab/data.gouv.fr/infra/tabular-api"
docker-repo-metrics:
type: string
default: "registry.gitlab.com/etalab/data.gouv.fr/infra/metrics-api"

jobs:
lint:
Expand Down Expand Up @@ -64,33 +73,81 @@ jobs:
path: reports/python

build:
docker:
- image: ghcr.io/astral-sh/uv:python<< pipeline.parameters.python-version >>-trixie
machine:
image: ubuntu-2404:current
steps:
- checkout
- run:
name: Compute RELEASE_VERSION via setuptools_scm
name: Install uv and Python << pipeline.parameters.python-version >>
command: |
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv python install << pipeline.parameters.python-version >>
uv sync --frozen
- run:
name: Compute RELEASE_VERSION via setuptools_scm, and build wheel
command: |
export PATH="$HOME/.local/bin:$PATH"
uv pip install --system setuptools-scm
# derive from setuptools_scm or base version + build number
RELEASE_VERSION=$(python -m setuptools_scm)
echo "Building a wheel release with version $RELEASE_VERSION"
echo "Build number: $CIRCLE_BUILD_NUM"
echo "Commit hash: ${CIRCLE_SHA1:0:7}"
echo "Git tag: $CIRCLE_TAG"
- run:
name: Build a distributable package as a wheel release
command: |
echo "RELEASE_VERSION=$RELEASE_VERSION" >> "$BASH_ENV"
uv build --wheel
# Build already executed above; artifacts are in dist/
- store_artifacts:
path: dist
- persist_to_workspace:
root: .
paths:
- .
- dist
- pyproject.toml
- run:
name: Log in to Docker registry
command: |
echo "${DOCKER_REGISTRY_PASSWORD}" | docker login -u "oauth2" --password-stdin << pipeline.parameters.docker-registry-host >>
- run:
name: Build and push Docker images (Tabular API and Metrics API)
command: |
source "$BASH_ENV"
export PATH="$HOME/.local/bin:$PATH"
# Five distinct tags per image (SCM version, SHA short/long, branch, optional git tag).
# Docker tags allow only [a-zA-Z0-9_.-]; setuptools_scm can output e.g. 0.4.0.dev5+gabc1234
DOCKER_TAG_SCM=$(echo "$RELEASE_VERSION" | tr '+' '-')
DOCKER_TAG_SHA_SHORT="${CIRCLE_SHA1:0:7}"
DOCKER_TAG_SHA_LONG="$CIRCLE_SHA1"
DOCKER_TAG_BRANCH=$(printf '%s' "$CIRCLE_BRANCH" | tr '/' '-' | sed 's/[^a-zA-Z0-9_.-]/-/g')
DOCKER_TAG_GIT="${CIRCLE_TAG:-}"
# Tabular API: tabular endpoints only
docker build --build-arg APP_MODULE=api_tabular.tabular.app:app_factory \
-t "<< pipeline.parameters.docker-repo-tabular >>:${DOCKER_TAG_SCM}" \
-t "<< pipeline.parameters.docker-repo-tabular >>:${DOCKER_TAG_SHA_SHORT}" \
-t "<< pipeline.parameters.docker-repo-tabular >>:${DOCKER_TAG_SHA_LONG}" \
-t "<< pipeline.parameters.docker-repo-tabular >>:${DOCKER_TAG_BRANCH}" \
${DOCKER_TAG_GIT:+-t "<< pipeline.parameters.docker-repo-tabular >>:${DOCKER_TAG_GIT}"} \
.
docker push "<< pipeline.parameters.docker-repo-tabular >>:${DOCKER_TAG_SCM}"
docker push "<< pipeline.parameters.docker-repo-tabular >>:${DOCKER_TAG_SHA_SHORT}"
docker push "<< pipeline.parameters.docker-repo-tabular >>:${DOCKER_TAG_SHA_LONG}"
docker push "<< pipeline.parameters.docker-repo-tabular >>:${DOCKER_TAG_BRANCH}"
[ -n "$DOCKER_TAG_GIT" ] && docker push "<< pipeline.parameters.docker-repo-tabular >>:${DOCKER_TAG_GIT}"
# Metrics API: metrics endpoints only
docker build --build-arg APP_MODULE=api_tabular.metrics.app:app_factory \
-t "<< pipeline.parameters.docker-repo-metrics >>:${DOCKER_TAG_SCM}" \
-t "<< pipeline.parameters.docker-repo-metrics >>:${DOCKER_TAG_SHA_SHORT}" \
-t "<< pipeline.parameters.docker-repo-metrics >>:${DOCKER_TAG_SHA_LONG}" \
-t "<< pipeline.parameters.docker-repo-metrics >>:${DOCKER_TAG_BRANCH}" \
${DOCKER_TAG_GIT:+-t "<< pipeline.parameters.docker-repo-metrics >>:${DOCKER_TAG_GIT}"} \
.
docker push "<< pipeline.parameters.docker-repo-metrics >>:${DOCKER_TAG_SCM}"
docker push "<< pipeline.parameters.docker-repo-metrics >>:${DOCKER_TAG_SHA_SHORT}"
docker push "<< pipeline.parameters.docker-repo-metrics >>:${DOCKER_TAG_SHA_LONG}"
docker push "<< pipeline.parameters.docker-repo-metrics >>:${DOCKER_TAG_BRANCH}"
[ -n "$DOCKER_TAG_GIT" ] && docker push "<< pipeline.parameters.docker-repo-metrics >>:${DOCKER_TAG_GIT}"

publish:
publish-pypi:
docker:
- image: ghcr.io/astral-sh/uv:python<< pipeline.parameters.python-version >>-trixie-slim
steps:
Expand All @@ -110,7 +167,11 @@ workflows:
requires:
- lint
- tests
- publish:
filters:
branches:
only: << pipeline.parameters.publish-branch >>
Comment thread
maudetes marked this conversation as resolved.
context: org-global
- publish-pypi:
requires:
- build
filters:
Expand Down
29 changes: 29 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM astral/uv:python3.11-trixie-slim

# Which app to run (e.g. api_tabular.tabular.app:app_factory or api_tabular.metrics.app:app_factory)
ARG APP_MODULE=api_tabular.tabular.app:app_factory

# install needed apt packages
RUN apt-get update -y && \
apt-get install -y --no-install-recommends git && \
rm -rf /var/lib/apt/lists/*

# create user & group
RUN groupadd --system datagouv && \
useradd --system --gid datagouv --create-home datagouv

# install
WORKDIR /home/datagouv
ADD . /home/datagouv/
# setuptools_scm runs git during install; Git refuses a repo whose owner differs from the build user (root).
RUN git config --global --add safe.directory /home/datagouv
RUN uv sync --frozen
RUN chown -R datagouv:datagouv /home/datagouv

# run (ENV from ARG so shell can expand APP_MODULE at runtime)
USER datagouv
ENV APP_MODULE=${APP_MODULE}
# Use `python -m gunicorn` instead of `gunicorn` due to uv issue #15246: https://github.com/astral-sh/uv/issues/15246
# Shell so APP_MODULE is expanded; bind to 8005 (map to different host ports when running both containers)
ENTRYPOINT ["/bin/sh", "-c"]
CMD ["uv run python -m gunicorn $APP_MODULE --bind 0.0.0.0:8005 --worker-class aiohttp.GunicornWebWorker --workers 2 --access-logfile -"]
35 changes: 24 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,39 @@ The production API is deployed on data.gouv.fr infrastructure at [`https://tabul
- **[uv](https://docs.astral.sh/uv/)** for dependency management
- **Docker & Docker Compose**

### Docker Compose profiles

| Profile | Use when | Services started |
|----------|----------|------------------|
| **test** | Run locally with the test database (PostgreSQL + PostgREST + optional Tabular API + Metrics API in Docker) | `postgres-test`, `postgrest-test`, `tabular-api`, `metrics-api` |
| **hydra** | Use a real Hydra CSV database; PostgREST only in Docker, run the API with uv on the host | `postgrest` |

**Run everything locally in Docker:** `docker compose --profile test up -d` → Tabular API at http://localhost:8005, Metrics API at http://localhost:8006, PostgREST at http://localhost:8080.

### 🧪 Run with a test database

1. **Start the Infrastructure**
1. **Start the stack**

With the **test** profile, you can either run the full stack in Docker (recommended for a quick local run) or only the infrastructure and run the API with uv (for development with hot reload).

Start the test CSV database and test PostgREST container:
**Option A – All in Docker (easiest for running locally):**
```shell
docker compose --profile test up -d
```
The `--profile test` flag tells Docker Compose to start the PostgREST and PostgreSQL services for the test CSV database. This starts PostgREST on port 8080, connecting to the test CSV database. You can access the raw PostgREST API on http://localhost:8080.
This starts the test PostgreSQL, PostgREST, Tabular API (port 8005), and Metrics API (port 8006). You can use the APIs at http://localhost:8005 and http://localhost:8006.

2. **Launch the main API proxy**

Install dependencies and start the proxy services:
**Option B – Only infrastructure in Docker, API with uv (for development):**
```shell
docker compose --profile test up -d postgres-test postgrest-test
```
PostgREST is available at http://localhost:8080. Then start the API proxy on the host:
```shell
uv sync
uv run adev runserver -p8005 api_tabular/tabular/app.py # Api related to apified CSV files by udata-hydra (dev server)
uv run adev runserver -p8006 api_tabular/metrics/app.py # Api related to udata's metrics (dev server)
uv run adev runserver -p8005 api_tabular/tabular/app.py # Tabular API (dev server)
uv run adev runserver -p8006 api_tabular/metrics/app.py # Metrics API (dev server)
```

**Note:** For production, use gunicorn with aiohttp worker:
**Note:** For production (on the host), use gunicorn with aiohttp worker:
```shell
# Tabular API (port 8005)
uv run gunicorn api_tabular.tabular.app:app_factory \
Expand All @@ -54,9 +67,9 @@ The production API is deployed on data.gouv.fr infrastructure at [`https://tabul
--access-logfile -
```

The main API provides a controlled layer over PostgREST - exposing PostgREST directly would be too permissive, so this adds a security and access control layer.
The API provides a controlled layer over PostgREST (security and access control); exposing PostgREST directly would be too permissive.

3. **Test the API**
2. **Test the API**

Query the API using a `resource_id`. Several test resources are available in the fake database:

Expand Down
32 changes: 32 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,35 @@ services:
- POSTGRES_PASSWORD=csvapi
profiles:
- test

# Tabular API (run locally via Docker, uses same Dockerfile as CI)
tabular-api:
build:
context: .
dockerfile: Dockerfile
args:
APP_MODULE: api_tabular.tabular.app:app_factory
ports:
- "8005:8005"
environment:
- PGREST_ENDPOINT=http://postgrest-test:8080
depends_on:
- postgrest-test
profiles:
- test

# Metrics API (run locally via Docker, uses same Dockerfile as CI)
metrics-api:
build:
context: .
dockerfile: Dockerfile
args:
APP_MODULE: api_tabular.metrics.app:app_factory
ports:
- "8006:8005"
environment:
- PGREST_ENDPOINT=http://postgrest-test:8080
depends_on:
- postgrest-test
profiles:
- test