> ## Documentation Index
> Fetch the complete documentation index at: https://anaconda.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Asset promotion

Promote assets across project branches with `promote_assets`, carrying metadata and aliases.

```python theme={null}
from obproject.assets import promote_assets

promote_assets(project, source, target, alias="candidate")
```

Promote assets from one branch to another by copying metadata pointers (the underlying data is not duplicated). Each promoted instance gets an alias on the target branch for stable referencing.

Parameter

Type

Default

Description

`project`

str

—

Project name

`source`

str

—

Source branch name

`target`

str

—

Target branch name

`kinds`

list

`["data", "models"]`

Asset types to promote

`asset`

str

`None`

Specific asset name, or all if omitted

`instance`

str

`"latest"`

Instance to promote (`"latest"`, ID, or `"@alias"`)

`alias`

str

`"candidate"`

Alias to set on the promoted instance. Must be in the allowed list. Set to `None` to skip.

`with_aliases`

bool

`False`

Copy existing aliases from source branch

**Returns:** `{"promoted": [...], "errors": [...]}`

Promoted instances are tagged with aliases that represent lifecycle stages:

Alias

Meaning

Typical setter

`@candidate`

Promoted from a branch, ready for evaluation

`promote_assets()` (default)

`@validated`

Passed quality gates

Evaluation flow

`@production`

Actively consumed by downstream flows/apps

Approval step

```python theme={null}
# Feature branch merges - model arrives on main as @candidate
promote_assets('my_project', source='feature-v2', target='main')

# Evaluation flow passes - re-alias to @validated
promote_assets('my_project', source='main', target='main',
               asset='classifier', instance='@candidate',
               alias='validated')

# Manual approval - promote to @production
promote_assets('my_project', source='main', target='main',
               asset='classifier', instance='@validated',
               alias='production')
```

Downstream consumers can then read a specific stage:

```python theme={null}
model = self.prj.get_model("classifier", instance="@production")
```

To customize the allowed aliases, add to `obproject.toml`:

```toml theme={null}
[promotion]
aliases = ["candidate", "validated", "production"]  # default
```

Add a `promote` job to your GitHub Actions workflow that runs before teardown when a PR is merged:

```yaml theme={null}
promote:
  if: >
    github.event_name == 'pull_request' &&
    github.event.action == 'closed' &&
    github.event.pull_request.merged == true
  steps:
    # ... setup steps ...
    - name: Promote assets to main
      run: |
        BRANCH=${{ github.head_ref }}
        PROJECT=$(yq .project obproject.toml)
        python -c "
        from obproject.assets import promote_assets
        result = promote_assets('$PROJECT', source='$BRANCH', target='main')
        for p in result['promoted']:
            print(f\"Promoted {p['kind']}/{p['name']} with @{p.get('alias', 'candidate')}\")
        "

teardown:
  needs: promote
  # ... existing teardown job ...
```

This ensures assets are promoted to main with `@candidate` before the feature branch is torn down.

> [!TIP]
>
> **`[dev-assets]` and promotion pipelines:** `[dev-assets] branch = "main"` redirects all asset reads to main, which is ideal for consumer flows (dashboards, reports). But in a promotion pipeline where a flow trains a model and then evaluates it on the same branch, reads need to come from the branch that just wrote the asset. Either omit `[dev-assets]` in promotion projects, or use a try/except fallback to read from the write branch when the asset doesn't exist on main yet.
