Extension API reference
Registering actions
from gundi_action_runner import action
@action.auth(config=AuthConfig)
async def auth(integration, action_config): ...
@action.pull(config=PullConfig, title="Pull Observations")
async def pull_observations(integration, action_config): ...
@action.push(config=PushConfig)
async def push_positions(integration, action_config, data: PositionData, metadata): ...
@action.generic(config=MaintenanceConfig, id="run_maintenance")
async def maintenance(integration, action_config): ...
- Handlers must be
async. The action id defaults to the function name; override withid=. config=must subclass the matching base (AuthActionConfiguration,PullActionConfiguration,PushActionConfiguration,GenericActionConfigurationfromgundi_action_runner.actions.core).title=sets the display name registered in the Gundi portal.- Push actions must accept a
dataparameter annotated with a pydantic model, plus ametadataparameter. - Invalid registrations (duplicate id, wrong config base, missing
parameters, sync function) raise
RegistryErrorat import time with a message naming the offending function.
Decorator ordering
@action.* / @webhook must be the outermost (topmost) decorator — it
registers the exact function object it receives. Wrapping decorators go
below it:
@action.pull(config=PullConfig, title="Pull Observations")
@crontab_schedule("*/15 * * * *")
@activity_logger()
async def pull_observations(integration, action_config): ...
Registering a webhook handler
from gundi_action_runner import webhook
@webhook
async def webhook_handler(payload: MyPayload, integration, webhook_config: MyConfig): ...
One webhook handler per connector. Payload/config models are introspected
from the annotations (or passed explicitly:
@webhook(payload=MyPayload, config=MyConfig)).
Building the app
from gundi_action_runner import create_app
app = create_app(handlers_modules=["myconnector.handlers"])
Without arguments, create_app() reads GUNDI_HANDLERS_MODULES
(comma-separated import paths) and falls back to scanning the legacy
template convention (GUNDI_LEGACY_ACTIONS_MODULE /
GUNDI_LEGACY_WEBHOOKS_MODULE, defaulting to app.actions.handlers /
app.webhooks.handlers) when the registry is empty.
Framework services
| Import | Purpose |
|---|---|
gundi_action_runner.services.gundi |
send_observations_to_gundi(), send_events_to_gundi() |
gundi_action_runner.services.state.IntegrationStateManager |
Cursors / high-water marks between runs |
gundi_action_runner.services.activity_logger |
@activity_logger(), log_action_activity() |
gundi_action_runner.services.action_scheduler |
@crontab_schedule("*/15 * * * *") |
gundi_action_runner.services.utils |
FieldWithUIOptions, UIOptions, GlobalUISchemaOptions |
Error reporting
When a handler raises, the framework logs the failure to the portal activity log. Two levels of polish are available:
Automatic classification. Failures during handler execution are classified heuristically: HTTP 401/403 responses render as authentication failures, 429 as rate limiting, 5xx as a bad provider response, and connection/timeout exceptions as connectivity problems. Operators see a short, human-first message ("Authentication failed — ... (HTTP 401)") instead of a raw traceback; full details are still captured alongside.
Explicit classification. For precise control, raise one of the
IntegrationError subclasses from your handler:
from gundi_action_runner.services.errors import (
IntegrationAuthError, # "Authentication failed"
IntegrationConnectionError, # "Could not reach the provider"
IntegrationRateLimitError, # "Rate limited by the provider"
IntegrationBadResponseError, # "Unexpected response from the provider"
)
@action.pull(config=PullConfig, title="Pull Observations")
async def pull_observations(integration, action_config):
response = await client.fetch(...)
if response.status_code == 401:
raise IntegrationAuthError("API key rejected", status_code=401)
...
Explicitly raised IntegrationErrors always win over the heuristics and
classify in every context (the heuristics only apply to handler execution,
so e.g. a 401 from Gundi's own portal API is never misreported as a
provider auth failure). The same classification also flows through
@activity_logger()-decorated handlers.
Testing your connector
Installing gundi-action-runner registers a pytest plugin exposing the
framework's fixtures (integration_v2, mock_gundi_client_v2,
mock_publish_event, ...) with no conftest wiring. Test deps ship as an
extra:
pip install "gundi-action-runner[testing]"