Skip to content

fix: don't emit external models as multi-asset outputs (#65)#67

Open
Benrishty wants to merge 1 commit into
opensource-observer:mainfrom
Benrishty:fix/external-models-emitted-as-outs
Open

fix: don't emit external models as multi-asset outputs (#65)#67
Benrishty wants to merge 1 commit into
opensource-observer:mainfrom
Benrishty:fix/external-models-emitted-as-outs

Conversation

@Benrishty

Copy link
Copy Markdown

Fixes #65 (and the root cause behind #53).

Problem

SQLMeshController.non_external_models_dag() doesn't actually filter external models. Its only guard is if not model: continue, which skips FQNs that resolve to no model — but an external model resolves to a real Model (kind.is_external == True), so it passes through, and to_asset_outs() builds an AssetOut for it. External models are source tables the project reads, so emitting them as outputs is wrong: it materializes phantom assets and collides with any existing asset that already owns that key (e.g. a dlt asset landing the same physical table) → DagsterInvalidDefinitionError: Duplicate asset key.

This is what #65 observed (correctly spotting non_external_models_dag) and the symptom #53 hit.

Fix

Two small changes so externals become dependency-only edges:

  1. controller/base.pynon_external_models_dag() now skips model.kind.is_external (living up to its name). Externals are no longer emitted as outs, and also drop out of the materializable-model set resource.py derives from it.
  2. controller/dagster.pyto_asset_outs()'s dependency loop routes external-model deps down the asset-dep path. This is required, not cosmetic: an external dep resolves to a truthy dep.model, so with change (1) alone it would be added to internal_asset_deps without a matching out or dep, and Dagster rejects that (... specified as asset inputs, but are not specified in internal_asset_deps). Routing it through create_asset_dep keeps deps and internal_asset_deps consistent.

Verification

Tested against a real project with 5 external models (4 source tables + 1 cross-database dimension):

  • Before: externals emitted as outs; a translator that remaps an external onto a key owned by another asset → Duplicate asset key.
  • After: externals appear only in deps, never in outs; the multi-asset builds cleanly; the remapped-external case no longer collides.

Backward compatible: projects with no external models are unaffected — including this repo's sample/sqlmesh_project, so the existing test_sqlmesh_context_to_asset_outs assertions (deps == 1, outs == 10) are unchanged.

Note on tests

The sample project currently has no external model, which is why this path wasn't caught. I'm happy to add a regression test, but it needs a fixture with an external model (either extending the sample — which would shift the counts in the existing test — or a dedicated fixture). Let me know which you'd prefer and I'll push it in this PR.

…server#65)

`SQLMeshController.non_external_models_dag()` did not actually filter external
models — its only guard was `if not model`, which skips FQNs that resolve to no
model at all. External models resolve to a real `Model` (kind.is_external), so they
passed through and `to_asset_outs()` emitted an `AssetOut` for each. External models
are source tables the project READS, so materializing them as outputs is wrong and
collides with any existing asset that already owns that key (e.g. a dlt asset), which
surfaces as `DagsterInvalidDefinitionError: Duplicate asset key` (see opensource-observer#65, opensource-observer#53).

Two changes make externals dependency-only:

1. `non_external_models_dag()` now skips `model.kind.is_external`, so externals are
   no longer emitted as outs (and drop out of `resource.py`'s materializable-model
   set, which is also correct).
2. `to_asset_outs()`'s dependency loop now routes external-model deps to the asset-dep
   path. This is required: an external dep resolves to a truthy `dep.model`, so with
   only change (1) it would land in `internal_asset_deps` without a matching out OR
   dep — which Dagster rejects ("specified as asset inputs, but not in
   internal_asset_deps"). Routing it through `create_asset_dep` keeps deps and
   internal_asset_deps consistent.

Backward compatible: projects with no external models are unaffected (the sample
project has none, so its existing to_asset_outs test is unchanged).

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

External Model not Filtering out Correctly

1 participant