← Files astronomer-dataARCHIVED FILE

skills/migrating-dagster-to-airflow/scripts/tests/conftest.py

3.82 KB · Oct 3, 2026 · 06:37 UTC

↓ Download file

"""Shared fixtures for the migration-scripts test suite.

Self-contained: builds tiny synthetic Dagster projects under tmp_path so the
suite has no dependency on the repo's test-projects/ or example-migrations/ (which stay behind when
this skill subtree moves to astronomer/agents). The scripts are imported by
adding the parent scripts/ directory to sys.path.
"""

import sys
from pathlib import Path

import pytest

# Make the scripts (one level up from tests/) importable as top-level modules.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))


# A minimal Dagster project exercising the constructs the scanner must catch.
# It is never imported or executed: the static scan only parses text, so the
# `dagster` imports and decorators need not resolve.
_ASSETS = """\
import dagster as dg
from dagster import asset, multi_asset, AssetOut, AssetSpec, op


@asset
def raw_sales(context):
    return 1


@asset(io_manager_key="warehouse")
def daily_sales(context, raw_sales):
    # raw_sales is a signature-inferred upstream edge
    return raw_sales


@multi_asset(can_subset=True, outs={"out_a": AssetOut(), "out_b": AssetOut()})
def multi_outs():
    yield None


@multi_asset(specs=[AssetSpec("spec_x", deps=["raw_sales"]), AssetSpec("spec_y")])
def multi_specs():
    yield None


@op
def legacy_op(x):
    return x


@solid
def old_solid(x):
    return x
"""

_RESOURCES = """\
import dagster as dg


class BaseParquetIO(dg.ConfigurableIOManager):
    def handle_output(self, context, obj):
        pass

    def load_input(self, context):
        return None


class DerivedParquetIO(BaseParquetIO):
    prefix: str = "x"
"""

_FRESHNESS = """\
import dagster as dg

NEW_FP = dg.FreshnessPolicy.cron(deadline_cron="0 9 * * *")
LEGACY_FP = dg.LegacyFreshnessPolicy(maximum_lag_minutes=120)
"""

_CLOUD = """\
import os

IS_BRANCH = os.environ.get("DAGSTER_CLOUD_IS_BRANCH_DEPLOYMENT", "")
"""

_DEFINITIONS = """\
import dagster as dg
from dagster import Definitions, ScheduleDefinition, define_asset_job


warehouse_job = define_asset_job("warehouse_job", selection="*")

resources = {
    "io_manager": DerivedParquetIO(),
    "warehouse": dg.DuckDBPandasIOManager(database="x"),
}

defs = Definitions(
    schedules=[ScheduleDefinition(job=warehouse_job, cron_schedule="@weekly")],
    resources=resources,
)
"""

_BRANCHED = """\
import os
import dagster as dg
from dagster import define_asset_job
from dagster import sensor as sensor_decorator  # aliased import
from dagster_dbt import dbt_cloud_assets          # integration package origin


# name= kwarg / first-positional should win over the Python binding name
named_job = define_asset_job(name="the_real_job_name", selection="*")


# aliased decorator must still resolve to the dagster symbol `sensor`
@sensor_decorator(name="the_real_sensor_name")
def binding_sensor_name(context):
    ...


# import-time env branch selecting an alternate integration: BOTH branches must
# produce records, each flagged with its condition.
if os.getenv("ORCH_MODE") == "cloud":

    @dbt_cloud_assets(workspace=None)
    def cloud_models(context):
        ...

else:

    @dg.asset
    def local_only_asset():
        return 1
"""

_DEFS_YAML = """\
type: my_lib.ComponentA
attributes:
  cron: "@daily"
  key: alpha
---
type: my_lib.ComponentB
attributes:
  key: beta
"""


@pytest.fixture()
def synth_project(tmp_path):
    """Write the synthetic Dagster project and return its root path."""
    pkg = tmp_path / "pkg"
    pkg.mkdir()
    (pkg / "assets.py").write_text(_ASSETS)
    (pkg / "resources.py").write_text(_RESOURCES)
    (pkg / "freshness.py").write_text(_FRESHNESS)
    (pkg / "cloud.py").write_text(_CLOUD)
    (pkg / "definitions.py").write_text(_DEFINITIONS)
    (pkg / "branched.py").write_text(_BRANCHED)
    comps = pkg / "components"
    comps.mkdir()
    (comps / "defs.yaml").write_text(_DEFS_YAML)
    return tmp_path

SHA-256: ca04a73e44c1cd861bd2ad071a9983e5905b4bb6444662bfb4e73177520e4d92