Skip to content

Migrating an existing fork to the gundi-action-runner library

Forks of this template keep working without changes: merging upstream gives you compatibility shims (app/services/* etc. re-export the library, with DeprecationWarnings) and the framework rides in-tree under src/ until you migrate. Migration is optional and incremental.

Step 0 — merge upstream (nothing else changes)

After merging, your CI (inherited via .github/workflows/_tests.yml) runs pip install -e . --no-deps automatically. Your Dockerfile is fork-owned: add the equivalent lines (COPY pyproject.toml . / COPY ./src src/ / RUN pip install -e . --no-deps) before deploying, or the container will fail at startup since the app/* shims import gundi_action_runner. Your handlers, configurations, tests, and uvicorn app.main:app all keep working.

What to expect during the merge:

  • app/conftest.py will conflict if you appended custom fixtures (most forks did). Resolution is mechanical: keep the upstream star-import line AND your custom fixtures below it.
  • Your pytest run now also collects upstream suites (tests/, examples/ via the inherited pyproject.toml testpaths). They pass in a fork context and pin the compatibility contract — treat failures there as signals, not noise. Trim testpaths if you must.
  • DeprecationWarnings from app.* imports are expected — they mark the shim layer, which is removed after an announced window.

Step 1 — adopt decorators in place (optional, incremental)

Decorate handlers inside your existing app/actions/handlers.py; the legacy import fires the decorators, so decorated and action_-prefixed handlers can coexist in that file:

from gundi_action_runner import action

@action.pull(config=PullObservationsConfig, title="Pull Observations")
async def pull_observations(integration, action_config): ...   # was action_pull_observations

Caution — moving handlers to a NEW module: discovery via GUNDI_HANDLERS_MODULES skips the legacy scan once ANY action is registered. Don't split handlers across a new decorator module and a legacy module — move them all at once, or keep decorating in place.

Step 2 — cut over to the library layout

  1. Point discovery at your module: set GUNDI_HANDLERS_MODULES=myconnector.handlers (or app = create_app(handlers_modules=["myconnector.handlers"]) in main.py).
  2. Change app.* imports to gundi_action_runner.* (mechanical find/replace; the shims made both names the same module objects).
  3. Add gundi-action-runner~=X.Y to your requirements, delete the inherited src/ tree and app/ shims, keep only your connector code.

Behavior changes to know about

  • Handler discovery is lazy. The template scanned app.actions.handlers at import; the library populates on create_app() / register_integration_in_gundi() / first execute_action(). A broken import inside your handlers module now fails at first use instead of at process import — still loudly, just later.
  • Decorator ordering: @action.*/@webhook must be the outermost decorator (see the extension API).
  • Legacy discovery is stricter. action_-prefixed functions without an action_config parameter are skipped with a warning instead of being registered (and the action_title decorator itself is never mistaken for a handler when imported alongside them). If an action disappears after merging, check its signature.
  • Gundi OAuth tokens are cached and shared. With gundi-client-v2 >= 3.7 every Gundi client the runner builds — including a bare GundiClient() in your own code — shares one token per set of credentials, in process memory and in Redis database REDIS_TOKEN_CACHE_DB (default 2). Nothing to configure unless that database is taken: set REDIS_TOKEN_CACHE_DB, or GUNDI_TOKEN_CACHE_URL for a different backend (file:///dir, or "" for in-process only). Tests that count token requests get the process cache cleared between tests by the framework's pytest plugin.
  • Preferred OAuth variable names are GUNDI_OAUTH_*. OAUTH_* and KEYCLOAK_* still work; when both spellings are set the GUNDI_-prefixed one wins. Scaffolded .env examples emit the new names.
  • Batched fan-outs. trigger_actions(integration_id, action_id, configs) and publish_events(events, topic) publish one request per batch instead of one session, token and round trip per message; batches split on Pub/Sub's count and byte limits. Use them where you loop over trigger_action.
  • A scaffolded project carries the fork's CI. gundi-runner new emits .github/workflows/{pr.yaml,main.yaml,_tests.yml} mirroring the template's tests → build → update_hcl pipeline; a fork that cuts over to the library layout keeps its own workflows and only changes how tests install (pip install -e ".[dev]" instead of pip-compile).

If you customized framework files

Forks that edited app/services/* or other framework internals will see merge conflicts against the shim layer (upstream replaced those files with one-line re-exports). Resolve them in two steps: keep the shim, then re-apply your customization to the corresponding gundi_action_runner module — or drop it if upstream has since absorbed the fix. Framework changes now land in the library, so this porting step happens once; afterwards, framework updates arrive via pip install -U gundi-action-runner with no merges at all. - python -m app.register still works; gundi-runner register is its library-native replacement.