Files
ResInsight/docs/cloud-service-api.md
T

12 KiB

Cloud Service API -- Developer Python Setup

The Cloud Service API is a local FastAPI service that bridges cloud data into ResInsight. It lives in the scripts/ri-cloud-api submodule, and ResInsight starts it after you sign in to Sumo:

<python> -m uvicorn ri_cloud_api.main:app --host 127.0.0.1 --port <free port>

<python> is the interpreter from Preferences -> Scripting -> Python Executable Location, the same one used for rips. ResInsight installs nothing itself, so that environment has to be ready up front. The service code always runs from its source folder -- a development build uses the submodule in place, an installed build a copy of it under CloudServiceApi -- so only the dependencies are strictly required in the environment.

Prerequisites

  • Python 3.11 or later, which is what ri-cloud-api requires.
  • The submodule checked out: git submodule update --init.

Python Environment

  • Komodo: the release already provides an environment with the required packages. Point Python Executable Location at it and the service starts on its own; the rest of this document does not apply.
  • Custom: install into the environment you already use for rips, or into a dedicated one that Python Executable Location points at. The next section covers both.

Use uv and uv sync for an environment dedicated to the service, either of the other two recipes when it is shared with rips. All three need an environment to install into -- an existing one, or a new virtual environment:

python -m venv C:\venvs\venvRiCloudApi
C:\venvs\venvRiCloudApi\Scripts\activate

On Linux, source ~/venvs/venvRiCloudApi/bin/activate. Then set Python Executable Location to C:\venvs\venvRiCloudApi\Scripts\python.exe, or ~/venvs/venvRiCloudApi/bin/python.

Keep the environment out of the repository so git clean does not take it, and out of the install tree.

Installing the packages

Three ways, all installing ri-cloud-api and its two workspace libraries with their dependencies:

Command Installs Effect on the environment
uv sync exact versions from uv.lock removes anything not in the lock
uv pip install exact versions from uv.lock only adds
pip install version ranges from pyproject.toml only adds

Each runs from the folder holding ri_cloud_api, which differs between a development build and an installed build. The recipes below write <python> for the environment's interpreter -- the one from Python Executable Location, on Windows typically <venv>\Scripts\python.exe, on Linux <venv>/bin/python.

Installing the three ri-cloud-* packages is optional: ResInsight prepends every libs/*/src to PYTHONPATH and runs from the source folder regardless. Leaving them out keeps the environment smaller, at the cost of setting PYTHONPATH yourself when running the service by hand. Each recipe ends with that variant.

1. Using uv sync

cd ResInsight\scripts\ri-cloud-api        # development
cd <install prefix>\bin\CloudServiceApi   # installed

uv sync --active --no-dev --frozen

--active installs into the activated environment instead of creating a .venv beside the package. --no-dev skips the dev group -- ruff, mypy, pytest -- which uv includes unless asked not to; drop it when working on the API itself. --frozen installs what uv.lock pins without rewriting it, so the source folder stays clean even when the lock is behind pyproject.toml. The ri-cloud-* packages go in editable.

uv sync owns the environment: it removes anything not in uv.lock, so do not point --active at a shared one. --inexact keeps those packages, but uv pip install is the better tool there. And if the interpreter does not satisfy requires-python = ">=3.11", uv deletes and recreates the environment rather than reporting an error.

Dependencies only: add --no-install-workspace.

2. Using uv pip install

cd ResInsight\scripts\ri-cloud-api        # development
cd <install prefix>\bin\CloudServiceApi   # installed

uv export --frozen --no-dev --no-hashes --format requirements-txt -o <somewhere>\req.txt
uv pip install --python <python> -r <somewhere>\req.txt

uv pip install only adds and upgrades; it never removes anything missing from uv.lock, which makes it the right tool for an environment shared with rips or your own tooling.

  • The export lists the ri-cloud-* packages as paths relative to the package directory, so run the install from that same folder. They install editable.
  • --python names the target interpreter. Without it uv picks the activated or discovered environment. The target needs no pip of its own.
  • --frozen reads uv.lock without rewriting it. Write req.txt outside the source folder so it does not show up as an untracked change there.
  • Drop --no-dev for ruff, mypy and pytest. Do not add --exact, which turns on pruning.

Dependencies only: add --no-emit-workspace to the export.

3. Using pip

The required packages are declared in the ri-cloud-api repository, which is the source of truth for the version ranges: pyproject.toml for the service, libs/services/pyproject.toml for the service layer, and libs/core_utils/pyproject.toml for the shared utilities. Rather than repeating that list, let pip read it -- with the environment activated:

cd ResInsight\scripts\ri-cloud-api        # development
cd <install prefix>\bin\CloudServiceApi   # installed

pip install ./libs/core_utils ./libs/services .

Without activating it, <python> -m pip install ... does the same.

The local projects must be given explicitly and in that order, since ri-cloud-core-utils and ri-cloud-services are on no index. Installing . last brings in the third-party dependencies, uvicorn with the [standard] extra included. Unlike the uv recipes, pip copies the packages into site-packages, so source edits stop taking effect -- add -e to each path for editable installs.

Dependencies only: follow with pip uninstall -y ri-cloud-api ri-cloud-services ri-cloud-core-utils. Uninstalling does not remove what a package pulled in, so the dependencies remain.

Installed builds

cmake --install copies the whole submodule to <install prefix>\bin\CloudServiceApi, beside the executable, and an installed ResInsight runs that copy rather than your checkout. It is a plain file copy that keeps the layout the recipes need -- ri_cloud_api, libs/<lib>/src, pyproject.toml, uv.lock -- with .git*, __pycache__, .venv and .*_cache excluded.

Installing ResInsight installs no Python packages, so run one of the recipes above from CloudServiceApi. Two things to keep in mind, since an install tree is often read-only or replaced by the next install:

  • The submodule has to be checked out before the install runs, or CloudServiceApi ends up empty.
  • Keep the environment outside the install tree, and do not drop --active or --frozen -- without them uv writes a .venv into the install folder and rewrites uv.lock there.

Running the service by hand

With the environment activated, run from the same folder the recipes used -- the working directory ResInsight uses:

uvicorn ri_cloud_api.main:app --host 127.0.0.1 --port 8000

Without activating it, <python> -m uvicorn ri_cloud_api.main:app ... does the same. http://127.0.0.1:8000/alive is the endpoint ResInsight polls; it should answer {"status":"alive"}.

With only the dependencies installed, the workspace libraries under libs/<lib>/src are not on sys.path, and the service fails with ModuleNotFoundError: No module named 'ri_cloud_services'. Add them first, colon separated on Linux:

$env:PYTHONPATH = "libs\core_utils\src;libs\services\src"

ResInsight never needs this -- RiaCloudApiService sets it before launching, whichever way the environment was prepared.

Where ResInsight looks for the package

A folder containing ri_cloud_api, in this order:

  1. <ResInsight executable folder>/CloudServiceApi/
  2. Each entry in Preferences -> Scripting -> Shared Script Folder(s) (semicolon-separated), and a nested ri-cloud-api folder inside each

An installed build finds its own copy first without any configuration. A development build has no CloudServiceApi beside the executable and falls through to the preference -- add <repo>/scripts/ri-cloud-api, or the scripts folder above it.

The order matters: preferences live under one Ceetron/ResInsight key shared by every ResInsight on the machine, so an entry added for a development build is visible to an installed build as well, and probing the executable's own folder first keeps it from shadowing the shipped copy. The launch is logged with the directory it settled on:

Cloud API service: launching '... -m uvicorn ri_cloud_api.main:app ...' (working directory '...')

Keeping it up to date

Service code needs nothing -- it runs from the folder, so restarting the service picks it up. For an installed build, re-running the install refreshes that folder. Dependencies need the install re-run after git submodule update moves the submodule pointer, or after pyproject.toml or uv.lock changes. Nothing checks automatically, and a dependency added since you last installed surfaces only as an import error at startup, so check after updating:

uv sync --active --no-dev --check

It compares the environment against uv.lock, lists what is missing and exits non-zero when the two have diverged, without changing anything. Pass the same flags you installed with, or it reports the packages you deliberately left out as missing.

Without uv, <python> -m pip check catches a missing dependency only when the ri-cloud-* packages are installed, since they are what declare the requirements -- in a dependencies-only environment it reports no problem even when the service cannot import.

Troubleshooting

Service output is forwarded to the ResInsight log, prefixed Cloud API service:.

  • no Python executable configured -- Python Executable Location is empty.
  • 'ri_cloud_api' not found in any of: ... -- submodule missing, or not reachable from Shared Script Folder(s). The message lists every location probed. On an installed build it means CloudServiceApi is empty, because the submodule was not checked out when ResInsight was installed.
  • ModuleNotFoundError: No module named 'ri_cloud_services' -- PYTHONPATH not set for a manual run.
  • Any other ModuleNotFoundError -- dependencies not installed into the configured interpreter, or installed into a different one than Python Executable Location points at.
  • The service starts but runs code you did not expect -- check the working directory in the launch log. An installed build always prefers its own CloudServiceApi over Shared Script Folder(s).
  • A uv sync or uv pip install that fails on a cached package -- uv cache clean empties uv's cache so the next command re-downloads. It discards cached downloads and build artifacts only, never installed environments; the cost is a slower next install.