This guide describes how to record pipeline executions in ADOC with acceldata-sdk-python.
Introduction
ADOC treats data pipelines as first-class observability objects. Each definition captures how work is structured (assets and jobs), and each run captures what happened during a given execution. acceldata-sdk-python creates those definitions, opens runs, attaches hierarchical spans, emits metrics and custom events, and closes runs with a clear outcome, so the Catalog and Pipelines views in ADOC stay aligned with your orchestration code.
Core Concepts
Concept | What It Represents |
|---|
Pipeline | The durable definition of an ETL or processing flow: jobs, inputs and outputs, and how they connect. ADOC uses this definition to reason about lineage across versions. |
Pipeline run | One execution of that definition. The same pipeline can run many times; each run gets its own identity and a span tree. |
Job | A unit of work inside a run. Inputs and outputs can point at catalog assets or at other jobs. You can optionally create a job together with a dedicated span for tighter timing and failure correlation. |
Span | A hierarchical node under a run used to group steps and metrics. Child spans model nested phases without losing the parent context. |
Span events | Structured signals attached to a span (GenericEvent, LogEvent, and lifecycle transitions). These record row counts, timings, checkpoints, or domain-specific markers during execution. |
Pipeline APIs focus on orchestration observability: definitions, runs, spans, jobs, and events. Catalog APIs focus on what is stored and how it is profiled or crawled. Most production setups use both: you register and profile assets through catalog calls, while pipeline calls describe how those assets are produced or consumed in each batch or stream.
The rest of this guide is organized by topic: definition, runs, spans, jobs, events, completion, and retrieval. A full execution typically defines or loads a pipeline, opens a run, records spans and jobs, emits events as work proceeds, and then updates the run to a terminal state.
Creating a Pipeline
Use CreatePipelineInputRequest to define and create a pipeline in ADOC. This request includes required identifiers along with several optional fields for metadata and organization.
Key Fields
Field | Description |
|---|
uid
| Unique identifier for the pipeline. |
name
| Display name of the pipeline. |
description (optional)
| Human-readable description of the pipeline. |
meta (optional)
| Additional metadata, such as owner, team, or code location. |
tags (optional)
| List of tags used for discovery and categorization in ADOC. |
Tags (Optional)
Use the tags field to attach metadata for discovery and display in the ADOC UI.
Property | Description |
|---|
tags
| A list of Tag objects. Each tag includes name (a stable identifier, typically in key:value format) and displayName (a human-readable label shown in the UI). |
If you don't provide tags, ADOC creates the pipeline without any tags.
from acceldata.client.adoc_client import AdocClient
from acceldata.models.api.pipeline.meta import Meta
from acceldata.models.api.pipeline.tag import Tag
from acceldata.models.sdk.pipeline.create_pipeline_input_request import (
CreatePipelineInputRequest,
)
client = AdocClient(
url="https://<your-adoc-url>",
access_key="<your-access-key>",
secret_key="<your-secret-key>",
)
pipeline_resource = client.create_pipeline(
CreatePipelineInputRequest(
uid="e2e_customers_pipeline",
name="E2E customers pipeline",
description="End-to-end pipeline lifecycle example",
meta=Meta(owner="data-team", team="analytics", codeLocation="..."),
tags=[
Tag(name="env:prod", displayName="Environment: Production"),
Tag(name="domain:finance", displayName="Domain: Finance"),
],
)
)
Replacing Tags
from acceldata.models.api.pipeline.tag import Tag
# Replacing tags:
pipeline_resource = pipeline_resource.replace_tags(
[
Tag(name="env:stage", displayName="Environment: Staging"),
Tag(name="owner:data-platform", displayName="Owner: Data Platform"),
]
)
Pipeline Runs
run_resource = pipeline_resource.create_pipeline_run()
LOGGER.info("Started pipeline run id=%s", run_resource.id)
PipelineRunResource exposes helpers such as create_root_span(), get_root_span(), and update_pipeline_run().
Creating a root span automatically initializes it with the current timestamp. You can attach additional metadata to the span by sending events.
root_span_resource = run_resource.create_root_span(uid="snippet_customers_root")
root_span_resource.send_event(LogEvent("Run started", context_data={"run_id": run_resource.id}))
Child Spans (SpanResource.create_child_span)
Argument | Description |
|---|
uid
| Unique child span ID (required, positional). |
associated_job_uids
| Optional list of job UIDs to attach to the child span. |
with_explicit_time, created_at, updated_at
| Same semantics as top-level span creation, used for backfills. |
Span Helpers and Run Completion
SpanResource exposes is_root(), has_children(), end(), abort(), and failed() to mirror orchestration state. Use GenericEvent for domain metrics and LogEvent for log lines. On failure, prefer failed() or abort() over end(). Close the root span and the run when the work finishes.
from datetime import datetime
from acceldata.models.sdk.pipeline.events.generic_event import GenericEvent
from acceldata.models.sdk.pipeline.pipeline_run_input import (
PipelineRunResult,
PipelineRunStatus,
)
pipeline_run = pipeline_resource.create_pipeline_run()
# get_root_span() returns the run root when present
root_span = run_resource.get_root_span()
# is_root returns True if the span is root, otherwise False
is_root = bool(root_span.is_root())
# has_children returns True if the span has children, otherwise False
has_children = bool(root_span.has_children())
span_ctx = run_resource.create_root_span(uid="monthly.generate.data.span")
span_ctx.start()
child_span = span_ctx.create_child_span("monthly.generate.customer.span")
child_span.send_event(
GenericEvent(
event_uid="order.customer.join.result",
context_data={"client_time": str(datetime.now()), "row_count": 100},
)
)
child_span.end()
span_ctx.end()
run_resource.update_pipeline_run(
context_data={"name": "backend", "key1": "value2"},
result=PipelineRunResult.SUCCESS,
status=PipelineRunStatus.COMPLETED,
)
pipeline.create_job and pipeline_run.create_job perform the same operation; only the run binding differs. On PipelineResource, CreateJobInput.pipeline_run_id is required. On PipelineRunResource, it is optional and defaults to that run's ID (when you set it, it must match).
| pipeline.create_job(CreateJobInput(...))
| pipeline_run.create_job(CreateJobInput(...))
|
|---|
Run binding | pipeline_run_id is required.
| pipeline_run_id is optional (defaults to this run; must match when set).
|
Typical context | A PipelineResource and an explicit run ID are available. | pipeline_run is already available from create_pipeline_run() or a fetch.
|
CreateJobInput Fields
Field | Description |
|---|
uid
| Unique job ID for the run (required). |
name
| Human-readable name (required). |
description
| Longer explanation of the job. |
inputs / outputs
| Lists of JobInputOutputRef. Reference the UID of an asset with asset_uid, or the UID of another job with job_uid. |
meta
| Structured ownership or source metadata (Meta). |
context
| Arbitrary key/value bag stored with the job. |
pipeline_run_id
| Run ID. Mandatory on pipeline.create_job, optional on pipeline_run.create_job. |
bounded_by_span
| When True, the SDK also creates a span under the root span bound to this job. If a root span is absent, the SDK creates one first and binds the job to a span under it. Default: False. |
span_uid
| Required when bounded_by_span=True; must be unique within the run. |
with_explicit_time
| For backfills. Set alongside the historical span and job guidance so timestamps are explicit. |
span_created_at / span_updated_at
| Optional span row timestamps, used when bounded_by_span=True and you control timestamps explicitly. |
Example Usage
from acceldata.models.api.pipeline.meta import Meta
from acceldata.models.sdk.pipeline.create_job_input import CreateJobInput, JobInputOutputRef
node_from_pipeline = pipeline_resource.create_job(
CreateJobInput(
uid="monthly_sales_aggregate",
name="Monthly sales aggregate",
description="Builds monthly aggregates for reporting.",
pipeline_run_id=run_resource.id,
inputs=[JobInputOutputRef(asset_uid="warehouse_ds.db.schema.source_table")],
outputs=[JobInputOutputRef(job_uid="downstream_job_uid")],
meta=Meta(owner="analytics", team="finance", code_location="..."),
context={"batch": "2026-04"},
bounded_by_span=True,
span_uid="monthly_sales_aggregate_span",
)
)
node_from_run = run_resource.create_job(
CreateJobInput(
uid="monthly_sales_aggregate_r",
name="Monthly sales aggregate",
description="Same job with a shorter call site when the run object is already available.",
inputs=[JobInputOutputRef(asset_uid="warehouse_ds.db.schema.source_table")],
outputs=[JobInputOutputRef(job_uid="downstream_job_uid")],
meta=Meta(owner="analytics", team="finance", code_location="..."),
context={"batch": "2026-04"},
bounded_by_span=True,
span_uid="monthly_sales_aggregate_r_span",
)
)
Use bounded_by_span=True to create a job and its span together, then fetch the span by UID.
from acceldata.models.api.pipeline.meta import Meta
from acceldata.models.sdk.pipeline.create_job_input import CreateJobInput, JobInputOutputRef
from acceldata.models.sdk.pipeline.events.generic_event import GenericEvent
job_node = run_resource.create_job(
CreateJobInput(
uid="build_reports",
name="Build reports",
description="Materializes monthly reporting tables from the curated layer.",
pipeline_run_id=run_resource.id,
bounded_by_span=True,
with_explicit_time=False,
span_uid="build_reports_span",
inputs=[JobInputOutputRef(asset_uid="snowflake_ds.raw.input_table")],
outputs=[JobInputOutputRef(asset_uid="snowflake_ds.curated.output_table")],
meta=Meta(owner="data-team", team="analytics", code_location="..."),
context={"batch_id": "2026-04", "downstream": "looker"},
)
)
job_span = run_resource.get_span("build_reports_span")
# Span creation automatically starts the span.
job_span.send_event(
GenericEvent(
event_uid="build_reports_started",
context_data={"job_uid": job_node.uid},
)
)
Alternative with bounded_by_span=False — create the span explicitly and associate the job UID:
from acceldata.models.api.pipeline.meta import Meta
from acceldata.models.sdk.pipeline.create_job_input import CreateJobInput, JobInputOutputRef
from acceldata.models.sdk.pipeline.events.generic_event import GenericEvent
manual_job = run_resource.create_job(
CreateJobInput(
uid="build_reports_manual",
name="Build reports manual span binding",
description="Same job pattern with a child span created explicitly under the root span.",
pipeline_run_id=run_resource.id,
bounded_by_span=False,
with_explicit_time=False,
inputs=[JobInputOutputRef(asset_uid="s3_ds.staging.customers_raw_snapshot")],
outputs=[JobInputOutputRef(asset_uid="snowflake_ds.warehouse.customers_curated")],
meta=Meta(owner="data-team", team="analytics", code_location="..."),
context={"bind_mode": "manual_span"},
)
)
root_span = run_resource.get_root_span()
# Span creation automatically starts the span.
manual_job_span = root_span.create_child_span(
uid="build_reports_manual_span",
associated_job_uids=[manual_job.uid],
)
manual_job_span.send_event(
GenericEvent(
event_uid="manual_job_started",
context_data={"job_uid": manual_job.uid},
)
)
manual_job_span.end()
Set asset_uid on JobInputOutputRef(asset_uid=...) by copying the value from the ADOC UI.
Child Spans Under a Job
Child spans under a job span provide phase-level tracking (extract, transform, publish, and so on) while keeping the job as the parent context.
# When this block runs alone, load the bounded job span first.
job_span = run_resource.get_span("build_reports_span")
job_span.start()
# Span creation automatically starts the span.
extract_span = job_span.create_child_span(
uid="build_reports_extract_span",
associated_job_uids=[job_node.uid],
)
extract_span.send_event(
GenericEvent(
event_uid="extract_phase_started",
context_data={"step": "source_extract"},
)
)
extract_span.end()
# Span creation automatically starts the span.
publish_span = job_span.create_child_span(
uid="build_reports_publish_span",
associated_job_uids=[job_node.uid],
)
publish_span.send_event(
GenericEvent(
event_uid="publish_phase_started",
context_data={"step": "warehouse_publish"},
)
)
publish_span.end()
job_span.end()
Span Events
LogEvent carries log-style text. GenericEvent carries a stable event_uid plus arbitrary context_data for domain metrics or checkpoints.
root_span_resource.send_event(LogEvent("Started report generation", context_data={"batch": "2026-04"}))
root_span_resource.send_event(
GenericEvent(
event_uid="reports_generated",
context_data={"tables_created": 4},
)
)
Ending Spans
Call end() when the span's work finishes normally. Use failed() or abort() to record an error or cancellation instead of a clean completion.
root_span_resource.end(context_data={"dag_status": "SUCCESS"})
Completing the Pipeline Run
from acceldata.models.sdk.pipeline.pipeline_run_input import (
PipelineRunResult,
PipelineRunStatus,
)
run_resource.update_pipeline_run(
result=PipelineRunResult.SUCCESS,
status=PipelineRunStatus.COMPLETED,
)
update_pipeline_run() returns the same PipelineRunResource with an updated payload. It accepts both result and status:
status is the current lifecycle state of the run (for example, STARTED, COMPLETED, FAILED, ABORTED).
result is the final business outcome (for example, SUCCESS or FAILURE) and applies when the run is terminal.
When you omit both, the SDK defaults to result=SUCCESS and status=COMPLETED.
While work is still in flight, keep status=STARTED and typically result=RUNNING. When the run succeeds, set status=COMPLETED and result=SUCCESS. On failure, use status=FAILED with result=FAILURE.
Retrieve and Manage Existing Pipelines and Runs
Resolve a Pipeline (client.get_pipeline)
pipeline_identity is the string the server accepts to locate a single pipeline — typically the pipeline UID from creation, or the pipeline ID as a string when only the numeric key is available. The returned PipelineResource exposes create_pipeline_run, get_latest_pipeline_run, get_runs, and create_job (with an explicit pipeline_run_id on the input).
pipeline_resource = client.get_pipeline("monthly_reporting_pipeline")
Get Latest Pipeline Run
Parameter | Description |
|---|
pipeline_run_id
| Run ID of the pipeline run. |
continuation_id
| Continuation ID of the pipeline run. |
pipeline_id
| ID of the pipeline the run belongs to. |
pipeline_resource = client.get_pipeline("monthly_reporting_pipeline")
pipeline_run = pipeline_resource.get_latest_pipeline_run()
pipeline_run = client.get_pipeline_run(pipeline_run_id=run_resource.id)
pipeline_run = client.get_pipeline_run(
continuation_id="demo_do_not_end_pipeline_run_8",
pipeline_id=pipeline_resource.id,
)
pipeline_run = pipeline_resource.get_run(continuation_id="demo_do_not_end_pipeline_run_8")
Get Pipeline Details for a Run
pipeline_details = run_resource.get_details()
Get All Spans for a Run
pipeline_run_spans = run_resource.get_spans()
Get Runs for a Pipeline
runs = client.get_pipeline_runs(pipeline_resource.id)
runs = pipeline_resource.get_runs()
Get All Pipelines
get_pipelines() returns a listing object. Iterate pipelines for each row, including pipeline_summary with uid and name.
listing = client.get_pipelines()
for row in listing.pipelines:
print(row.pipeline_summary.uid, row.pipeline_summary.name)
Delete a Pipeline
delete_response = pipeline_resource.delete()
Jobs and span events require an active run and open spans. Creating jobs before a run exists, emitting events after a span has ended, or reusing job or span UIDs within the same run all produce confusing failures. Leaving the pipeline run without a terminal update_pipeline_run call also leaves ADOC with an ambiguous outcome.
What's Next
After you complete this section, explore:
End-to-End Pipeline Lifecycle Example Using acceldata-sdk-python – See a complete script that creates a pipeline, runs it, and closes it out.
Pipeline continuation ID (multi-script runs) – Learn how multiple scripts or services can update the same pipeline run.
Historical pipeline instrumentation with explicit times – Learn how to backfill past pipeline executions with explicit timestamps.
Datasources and Assets Guide – Learn how job inputs and outputs reference catalog assets.