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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@
/public/assets/
/assets/vendor/
###< symfony/asset-mapper ###
*.local
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

* [PR-3](https://github.com/itk-dev/rpa-process-overview/pull/3)
Mocked API with FastAPI and friends.

[Unreleased]: https://github.com/rimi-itk/rpa-process-overview
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,22 @@ task help
TASK_DOCKER_COMPOSE='itkdev-docker-compose'
TASK_COMPOSER_INSTALL_ARGS='--no-dev'
```

## API Mock

``` shell
docker compose up --build --detach --wait

curl "http://$(docker compose port api 8000)/openapi.json"
curl "http://$(docker compose port api 8000)/api/v1/process/"
curl "http://$(docker compose port api 8000)/api/v1/process/" --header 'x-api-key: a-not-so-secret-key'
```

Create some data:

``` shell
docker compose exec api uv run python -m src.api.create-data
curl "http://$(docker compose port api 8000)/api/v1/process/" --header 'x-api-key: a-not-so-secret-key'
```

See [api/README.md](api/README.md) for some more details.
2 changes: 2 additions & 0 deletions api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__
*.db
1 change: 1 addition & 0 deletions api/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
64 changes: 64 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# https://docs.astral.sh/uv/guides/integration/docker/

# https://docs.astral.sh/uv/guides/integration/docker/#installing-uv
FROM python:3.13-slim-trixie
COPY --from=ghcr.io/astral-sh/uv:0.8.22 /uv /uvx /bin/

# https://docs.astral.sh/uv/guides/integration/docker/#installing-a-project
ADD . /app

WORKDIR /app
RUN uv sync --locked

# https://docs.astral.sh/uv/guides/integration/fastapi/
CMD ["uv", "run", "fastapi", "run", "src/api"]

# Make port 8000 available to the world outside this container
EXPOSE 8000

# # https://github.com/astral-sh/uv-docker-example/blob/main/Dockerfile
# # Use a Python image with uv pre-installed
# FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim

# # Setup a non-root user
# RUN groupadd --system --gid 1042 nonroot \
# && useradd --system --gid 1042 --uid 1042 --create-home nonroot

# # Install the project into `/app`
# WORKDIR /app

# # Enable bytecode compilation
# ENV UV_COMPILE_BYTECODE=1

# # Copy from the cache instead of linking since it's a mounted volume
# ENV UV_LINK_MODE=copy

# # Ensure installed tools can be executed out of the box
# ENV UV_TOOL_BIN_DIR=/usr/local/bin

# # Install the project's dependencies using the lockfile and settings
# 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 --locked --no-install-project --no-dev

# # Then, add the rest of the project source code and install it
# # Installing separately from its dependencies allows optimal layer caching
# COPY . /app
# RUN --mount=type=cache,target=/root/.cache/uv \
# uv sync --locked --no-dev

# # Place executables in the environment at the front of the path
# ENV PATH="/app/.venv/bin:$PATH"

# # Reset the entrypoint, don't invoke `uv`
# ENTRYPOINT []

# # Use the non-root user to run our application
# USER nonroot

# # Run the FastAPI application by default
# # Uses `fastapi dev` to enable hot-reloading when the `watch` sync occurs
# # Uses `--host 0.0.0.0` to allow access from outside the container
# # Note in production, you should use `fastapi run` instead
# CMD ["fastapi", "dev", "--host", "0.0.0.0", "src/api"]
113 changes: 113 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# API

## Database

``` mermaid
---
title: RPA Process Overview
---
classDiagram
Process <|-- ProcessStep
Process <|-- ProcessRun
ProcessRun <|-- ProcessStepRun

class Process {
list[ProcessStep] steps
list[ProcessRun] runs
}

class ProcessStep {
Process process
int index
string name
}

class ProcessRun {
Process process
list[ProcessStepRun] steps
}

class ProcessStepRun {
ProcessRun run
string status
datetime started_at
datetime finished_at
JSON failure
}
```

``` text
GET /api/v1/process
?page=…
?q=…
GET /api/v1/process/{id}
# https://jsonapi.org/format/#fetching-relationships
GET /api/v1/process/{id}/relations/run
?page=…
?q=…
?id[]=…&id[]=…
(GET /api/v1/process/{id}/run/{id})
POST /api/v1/run/{id}/retry (/api/v1/process/{id}/run/{id}/retry)
(GET /api/v1/process/{id}/run/search)
```

<https://fastapi.tiangolo.com/virtual-environments/#create-a-virtual-environment>

``` shell
source .venv/bin/activate
pip install --requirement requirements.txt

pip install
```

``` shell name=update-run-step
curl --silent --verbose --location 'http://127.0.0.1:8000/api/v1/process/1/run/3/step/2' --header 'content-type: application/json' --data '
{
"status":"FAILED",
"started_at": "2025-09-25",
"failure": {
"code": 87,
"failed_at": "2025-09-25"
}
}
'

curl --silent --verbose --location 'http://127.0.0.1:8000/api/v1/process/1/run/3/step/0' --header 'content-type: application/json' --data '
{
"status":"SUCCESS",
"started_at": "2025-09-25"
}
'
```

## Security

Define API keys in `.env.local`, e.g.

``` dotenv
# .env.local
# Get a token from https://generate-random.org/api-token-generator or some such …
# Notice that the values must not be enclose in single quotes!
API_KEYS_READ=["a-not-so-secret-key", "759568492f338454603821a04810eabf"]
API_KEYS_WRITE=["3825e7be2d1ca130063171d8362ad4996e3a0df1e9f6dd2a4dc6bebf38bfc205"]
```

Restart the API to load the updated config.

Use a key:

``` shell
curl http://127.0.0.1:8000/api/v1/process/ --header 'x-api-key: a-not-so-secret-key'
```

> [!TIP]
> During development you may want to effectively disable authorization, and this can be done by adding `null` as a valid
> API key:
>
> ``` dotenv
> # .env.local
> API_KEYS_READ=[null]
> API_KEYS_WRITE=[null]
> ```
>
> Don't do this in production!
47 changes: 47 additions & 0 deletions api/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# https://taskfile.dev

version: "3"

tasks:
default:
cmds:
- task --list
silent: true

start:
cmds:
- uv sync
- task: start:dev
preconditions:
- sh: test -n "$VIRTUAL_ENV"
msg: |
This must be run inside a virtual environment.

Run

task venv:create

to create one or run

source .venv/bin/activate

to activate an existing virtual environment.

venv:create:
cmds:
- uv venv

dev:
cmds:
- uv run fastapi dev src/api

fixtures:load:
prompt: Really reset data?
cmds:
- uv run python -m src.api.create-data

lint:
cmds:
- ruff format src
- ruff check --select ALL check src --fix
# - ruff check --select ALL check src
27 changes: 27 additions & 0 deletions api/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[project]
name = "rpa-process-overview-api"
version = "0.1.0"
description = "RPA Process Overview API"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"fastapi-pagination>=0.14.1",
"fastapi[standard]>=0.117.1",
"sqlmodel>=0.0.25",
]

# https://docs.astral.sh/ruff/formatter/#philosophy
[tool.ruff]
line-length = 120

[tool.ruff.format]
quote-style = "single"
# https://peps.python.org/pep-0008/#indentation
# indent-style = "tab"
docstring-code-format = true

[dependency-groups]
dev = [
"faker>=37.8.0",
"ruff>=0.13.1",
]
8 changes: 8 additions & 0 deletions api/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Faker==37.8.0
flake8==7.3.0
isort==6.0.1
mccabe==0.7.0
pycodestyle==2.14.0
pyflakes==3.4.0
ruff==0.13.1
tzdata==2025.2
49 changes: 49 additions & 0 deletions api/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
aiosqlite==0.21.0
annotated-types==0.7.0
anyio==4.11.0
certifi==2025.8.3
click==8.3.0
dnspython==2.8.0
dpath==2.2.0
email-validator==2.3.0
fastapi==0.117.1
fastapi-cli==0.0.13
fastapi-cloud-cli==0.2.0
FastAPI-JSONAPI==3.0.0
fastapi-pagination==0.14.1
greenlet==3.2.4
h11==0.16.0
httpcore==1.0.9
httptools==0.6.4
httpx==0.28.1
idna==3.10
Jinja2==3.1.6
markdown-it-py==4.0.0
MarkupSafe==3.0.2
mdurl==0.1.2
orjson==3.11.3
pydanja==0.1.26
pydantic==2.11.9
pydantic_core==2.33.2
Pygments==2.19.2
python-dotenv==1.1.1
python-multipart==0.0.20
PyYAML==6.0.2
rich==14.1.0
rich-toolkit==0.15.1
rignore==0.6.4
ruff==0.13.1
sentry-sdk==2.38.0
shellingham==1.5.4
sniffio==1.3.1
SQLAlchemy==2.0.43
sqlmodel==0.0.25
starlette==0.48.0
typer==0.19.2
typing-inspection==0.4.1
typing_extensions==4.15.0
urllib3==2.5.0
uvicorn==0.37.0
uvloop==0.21.0
watchfiles==1.1.0
websockets==15.0.1
6 changes: 6 additions & 0 deletions api/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# https://flake8.pycqa.org/en/2.5.5/config.html#per-project
[flake8]
# ignore = E226,E302,E41
max-line-length = 160
# exclude = tests/*
# max-complexity = 10
Loading