dagster-drt 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,44 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.so
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ .eggs/
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # uv
17
+ .python-version
18
+ uv.lock
19
+
20
+ # drt runtime
21
+ .drt/
22
+ state.json
23
+
24
+ # Testing
25
+ .pytest_cache/
26
+ .coverage
27
+ htmlcov/
28
+ .mypy_cache/
29
+ .ruff_cache/
30
+
31
+ # IDE
32
+ .vscode/
33
+ .idea/
34
+ *.swp
35
+ *.swo
36
+
37
+ # OS
38
+ .DS_Store
39
+ Thumbs.db
40
+
41
+ # Secrets
42
+ *.env
43
+ .env*
44
+ !.env.example
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: dagster-drt
3
+ Version: 0.1.0
4
+ Summary: Community-maintained Dagster integration for drt (data reverse tool)
5
+ License: Apache-2.0
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: dagster>=1.6
14
+ Requires-Dist: drt-core>=0.4.1
15
+ Description-Content-Type: text/markdown
16
+
17
+ # dagster-drt
18
+
19
+ [![PyPI](https://img.shields.io/pypi/v/dagster-drt)](https://pypi.org/project/dagster-drt/)
20
+
21
+ Community-maintained [Dagster](https://dagster.io/) integration for [drt](https://github.com/drt-hub/drt) (data reverse tool).
22
+
23
+ Expose drt syncs as Dagster assets with full observability — metrics, dependencies, and dry-run support.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install dagster-drt
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```python
34
+ from dagster import Definitions
35
+ from dagster_drt import drt_assets
36
+
37
+ defs = Definitions(assets=drt_assets(project_dir="path/to/drt-project"))
38
+ ```
39
+
40
+ ## Features
41
+
42
+ ### DagsterDrtTranslator
43
+
44
+ Customise how drt syncs map to Dagster assets. Subclass and override methods:
45
+
46
+ ```python
47
+ from dagster import AssetKey
48
+ from dagster_drt import DagsterDrtTranslator, drt_assets
49
+
50
+ class MyTranslator(DagsterDrtTranslator):
51
+ def get_group_name(self, sync_config):
52
+ return "reverse_etl"
53
+
54
+ def get_deps(self, sync_config):
55
+ deps_map = {
56
+ "trigger_bq_meeting_gha": [AssetKey("bq_meeting_cloudsql")],
57
+ }
58
+ return deps_map.get(sync_config.name, [])
59
+
60
+ assets = drt_assets(
61
+ project_dir="pipeline/data-reverse",
62
+ dagster_drt_translator=MyTranslator(),
63
+ )
64
+ ```
65
+
66
+ **Available methods:**
67
+
68
+ | Method | Default | Purpose |
69
+ |--------|---------|---------|
70
+ | `get_asset_key(sync_config)` | `AssetKey(f"drt_{name}")` | Asset key |
71
+ | `get_group_name(sync_config)` | `None` | Group name |
72
+ | `get_description(sync_config)` | `sync_config.description` | Asset description |
73
+ | `get_deps(sync_config)` | `[]` | Upstream dependencies |
74
+ | `get_metadata(sync_config)` | `{}` | Static metadata |
75
+
76
+ ### Dry-Run Support (DrtConfig)
77
+
78
+ Control dry-run mode per-run from the Dagster UI, or set a build-time default:
79
+
80
+ ```python
81
+ # Build-time default: all syncs in dry-run mode
82
+ assets = drt_assets(project_dir="...", dry_run=True)
83
+
84
+ # Override per-run via Dagster UI → Run Config:
85
+ # ops:
86
+ # drt_my_sync:
87
+ # config:
88
+ # dry_run: false
89
+ ```
90
+
91
+ ### MaterializeResult
92
+
93
+ Assets return `MaterializeResult` with structured metadata visible in the Dagster UI:
94
+
95
+ - `sync_name` — sync identifier
96
+ - `rows_synced` — successful row count
97
+ - `rows_failed` — failed row count
98
+ - `rows_skipped` — skipped row count
99
+ - `dry_run` — whether dry-run was active
100
+ - `row_errors_count` — number of row-level errors (details in logs)
101
+
102
+ ### Filtering Syncs
103
+
104
+ ```python
105
+ # Only expose specific syncs as assets
106
+ assets = drt_assets(
107
+ project_dir="...",
108
+ sync_names=["sync_a", "sync_b"],
109
+ )
110
+ ```
111
+
112
+ ## Usage with dagster-dbt
113
+
114
+ ```python
115
+ from dagster import Definitions
116
+ from dagster_dbt import dbt_assets
117
+ from dagster_drt import drt_assets, DagsterDrtTranslator
118
+
119
+ defs = Definitions(
120
+ assets=[*my_dbt_assets, *drt_assets("path/to/drt-project")],
121
+ )
122
+ ```
123
+
124
+ ## API Reference
125
+
126
+ ### `drt_assets()`
127
+
128
+ | Parameter | Type | Default | Description |
129
+ |-----------|------|---------|-------------|
130
+ | `project_dir` | `str \| Path` | required | Path to drt project root |
131
+ | `sync_names` | `list[str] \| None` | `None` | Filter to specific syncs |
132
+ | `dagster_drt_translator` | `DagsterDrtTranslator \| None` | `None` | Custom translator |
133
+ | `dry_run` | `bool` | `False` | Default dry-run mode |
134
+
135
+ Returns: `list[AssetsDefinition]`
136
+
137
+ ## License
138
+
139
+ Apache-2.0
@@ -0,0 +1,123 @@
1
+ # dagster-drt
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/dagster-drt)](https://pypi.org/project/dagster-drt/)
4
+
5
+ Community-maintained [Dagster](https://dagster.io/) integration for [drt](https://github.com/drt-hub/drt) (data reverse tool).
6
+
7
+ Expose drt syncs as Dagster assets with full observability — metrics, dependencies, and dry-run support.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install dagster-drt
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```python
18
+ from dagster import Definitions
19
+ from dagster_drt import drt_assets
20
+
21
+ defs = Definitions(assets=drt_assets(project_dir="path/to/drt-project"))
22
+ ```
23
+
24
+ ## Features
25
+
26
+ ### DagsterDrtTranslator
27
+
28
+ Customise how drt syncs map to Dagster assets. Subclass and override methods:
29
+
30
+ ```python
31
+ from dagster import AssetKey
32
+ from dagster_drt import DagsterDrtTranslator, drt_assets
33
+
34
+ class MyTranslator(DagsterDrtTranslator):
35
+ def get_group_name(self, sync_config):
36
+ return "reverse_etl"
37
+
38
+ def get_deps(self, sync_config):
39
+ deps_map = {
40
+ "trigger_bq_meeting_gha": [AssetKey("bq_meeting_cloudsql")],
41
+ }
42
+ return deps_map.get(sync_config.name, [])
43
+
44
+ assets = drt_assets(
45
+ project_dir="pipeline/data-reverse",
46
+ dagster_drt_translator=MyTranslator(),
47
+ )
48
+ ```
49
+
50
+ **Available methods:**
51
+
52
+ | Method | Default | Purpose |
53
+ |--------|---------|---------|
54
+ | `get_asset_key(sync_config)` | `AssetKey(f"drt_{name}")` | Asset key |
55
+ | `get_group_name(sync_config)` | `None` | Group name |
56
+ | `get_description(sync_config)` | `sync_config.description` | Asset description |
57
+ | `get_deps(sync_config)` | `[]` | Upstream dependencies |
58
+ | `get_metadata(sync_config)` | `{}` | Static metadata |
59
+
60
+ ### Dry-Run Support (DrtConfig)
61
+
62
+ Control dry-run mode per-run from the Dagster UI, or set a build-time default:
63
+
64
+ ```python
65
+ # Build-time default: all syncs in dry-run mode
66
+ assets = drt_assets(project_dir="...", dry_run=True)
67
+
68
+ # Override per-run via Dagster UI → Run Config:
69
+ # ops:
70
+ # drt_my_sync:
71
+ # config:
72
+ # dry_run: false
73
+ ```
74
+
75
+ ### MaterializeResult
76
+
77
+ Assets return `MaterializeResult` with structured metadata visible in the Dagster UI:
78
+
79
+ - `sync_name` — sync identifier
80
+ - `rows_synced` — successful row count
81
+ - `rows_failed` — failed row count
82
+ - `rows_skipped` — skipped row count
83
+ - `dry_run` — whether dry-run was active
84
+ - `row_errors_count` — number of row-level errors (details in logs)
85
+
86
+ ### Filtering Syncs
87
+
88
+ ```python
89
+ # Only expose specific syncs as assets
90
+ assets = drt_assets(
91
+ project_dir="...",
92
+ sync_names=["sync_a", "sync_b"],
93
+ )
94
+ ```
95
+
96
+ ## Usage with dagster-dbt
97
+
98
+ ```python
99
+ from dagster import Definitions
100
+ from dagster_dbt import dbt_assets
101
+ from dagster_drt import drt_assets, DagsterDrtTranslator
102
+
103
+ defs = Definitions(
104
+ assets=[*my_dbt_assets, *drt_assets("path/to/drt-project")],
105
+ )
106
+ ```
107
+
108
+ ## API Reference
109
+
110
+ ### `drt_assets()`
111
+
112
+ | Parameter | Type | Default | Description |
113
+ |-----------|------|---------|-------------|
114
+ | `project_dir` | `str \| Path` | required | Path to drt project root |
115
+ | `sync_names` | `list[str] \| None` | `None` | Filter to specific syncs |
116
+ | `dagster_drt_translator` | `DagsterDrtTranslator \| None` | `None` | Custom translator |
117
+ | `dry_run` | `bool` | `False` | Default dry-run mode |
118
+
119
+ Returns: `list[AssetsDefinition]`
120
+
121
+ ## License
122
+
123
+ Apache-2.0
@@ -0,0 +1,4 @@
1
+ from dagster_drt.assets import DrtConfig, drt_assets
2
+ from dagster_drt.translator import DagsterDrtTranslator
3
+
4
+ __all__ = ["DagsterDrtTranslator", "DrtConfig", "drt_assets"]
@@ -0,0 +1,134 @@
1
+ """Expose drt syncs as Dagster assets."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Union
5
+
6
+ from dagster import (
7
+ AssetExecutionContext,
8
+ AssetsDefinition,
9
+ Config,
10
+ MaterializeResult,
11
+ MetadataValue,
12
+ asset,
13
+ )
14
+
15
+ from dagster_drt.translator import DagsterDrtTranslator
16
+
17
+
18
+ class DrtConfig(Config):
19
+ """Run configuration for drt assets — configurable from Dagster UI.
20
+
21
+ Follows the same pattern as dagster-dbt's ``--full-refresh`` flag.
22
+ """
23
+
24
+ dry_run: bool = False
25
+
26
+
27
+ def drt_assets(
28
+ project_dir: Union[str, Path],
29
+ sync_names: Union[list[str], None] = None,
30
+ dagster_drt_translator: DagsterDrtTranslator | None = None,
31
+ dry_run: bool = False,
32
+ ) -> list[AssetsDefinition]:
33
+ """Create Dagster assets from drt sync definitions.
34
+
35
+ Args:
36
+ project_dir: Path to drt project root.
37
+ sync_names: Optional filter. If None, discovers all syncs.
38
+ dagster_drt_translator: Translator for customising asset keys, groups,
39
+ deps, and metadata. If None, uses the default translator.
40
+ dry_run: Default dry-run mode. Can be overridden per-run via
41
+ ``DrtConfig`` in the Dagster UI.
42
+
43
+ Returns:
44
+ List of Dagster asset definitions.
45
+ """
46
+ from drt.config.parser import load_syncs
47
+
48
+ translator = dagster_drt_translator or DagsterDrtTranslator()
49
+ project_path = Path(project_dir)
50
+ syncs = load_syncs(project_path)
51
+ if sync_names:
52
+ syncs = [s for s in syncs if s.name in sync_names]
53
+
54
+ assets_list: list[AssetsDefinition] = []
55
+ for sync_config in syncs:
56
+ asset_key = translator.get_asset_key(sync_config)
57
+ group_name = translator.get_group_name(sync_config)
58
+ description = translator.get_description(sync_config)
59
+ deps = translator.get_deps(sync_config)
60
+ static_metadata = translator.get_metadata(sync_config)
61
+
62
+ asset_kwargs: dict[str, Any] = {
63
+ "key": asset_key,
64
+ "description": description,
65
+ }
66
+ if group_name is not None:
67
+ asset_kwargs["group_name"] = group_name
68
+ if deps:
69
+ asset_kwargs["deps"] = deps
70
+ if static_metadata:
71
+ asset_kwargs["metadata"] = static_metadata
72
+
73
+ def _make_asset_fn(
74
+ _sync_cfg: Any, _default_dry_run: bool
75
+ ) -> AssetsDefinition:
76
+ @asset(**asset_kwargs)
77
+ def _asset_fn(
78
+ context: AssetExecutionContext,
79
+ config: DrtConfig,
80
+ ) -> MaterializeResult:
81
+ from drt.cli.main import _get_destination, _get_source
82
+ from drt.config.credentials import load_profile
83
+ from drt.config.parser import load_project
84
+ from drt.engine.sync import run_sync
85
+ from drt.state.manager import StateManager
86
+
87
+ effective_dry_run = config.dry_run or _default_dry_run
88
+
89
+ project = load_project(project_path)
90
+ profile = load_profile(project.profile)
91
+ source = _get_source(profile)
92
+ destination = _get_destination(_sync_cfg)
93
+ state_mgr = StateManager(project_path)
94
+
95
+ result = run_sync(
96
+ _sync_cfg,
97
+ source,
98
+ destination,
99
+ profile,
100
+ project_path,
101
+ dry_run=effective_dry_run,
102
+ state_manager=state_mgr,
103
+ )
104
+
105
+ context.log.info(
106
+ f"drt sync '{_sync_cfg.name}': "
107
+ f"{result.success} synced, {result.failed} failed, "
108
+ f"{result.skipped} skipped (dry_run={effective_dry_run})"
109
+ )
110
+
111
+ # Log row-level error details for debugging
112
+ for row_error in result.row_errors:
113
+ context.log.warning(
114
+ f"Row error in '{_sync_cfg.name}': {row_error}"
115
+ )
116
+
117
+ return MaterializeResult(
118
+ metadata={
119
+ "sync_name": MetadataValue.text(_sync_cfg.name),
120
+ "rows_synced": MetadataValue.int(result.success),
121
+ "rows_failed": MetadataValue.int(result.failed),
122
+ "rows_skipped": MetadataValue.int(result.skipped),
123
+ "dry_run": MetadataValue.bool(effective_dry_run),
124
+ "row_errors_count": MetadataValue.int(
125
+ len(result.row_errors)
126
+ ),
127
+ }
128
+ )
129
+
130
+ return _asset_fn
131
+
132
+ assets_list.append(_make_asset_fn(sync_config, dry_run))
133
+
134
+ return assets_list
@@ -0,0 +1,47 @@
1
+ """DagsterDrtTranslator — controls how drt syncs map to Dagster assets.
2
+
3
+ Follows the same Translator pattern as dagster-dbt and dagster-dlt.
4
+ Subclass and override methods to customise asset keys, groups, deps, etc.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Mapping, Sequence
10
+ from typing import Any
11
+
12
+ from dagster import AssetKey
13
+
14
+ from drt.config.models import SyncConfig
15
+
16
+
17
+ class DagsterDrtTranslator:
18
+ """Default translator from drt SyncConfig to Dagster asset properties.
19
+
20
+ Override any method in a subclass to customise behavior::
21
+
22
+ class MyTranslator(DagsterDrtTranslator):
23
+ def get_group_name(self, sync_config):
24
+ return "reverse_etl"
25
+
26
+ drt_assets(project_dir="...", dagster_drt_translator=MyTranslator())
27
+ """
28
+
29
+ def get_asset_key(self, sync_config: SyncConfig) -> AssetKey:
30
+ """Return the Dagster AssetKey for a sync."""
31
+ return AssetKey(f"drt_{sync_config.name}")
32
+
33
+ def get_group_name(self, sync_config: SyncConfig) -> str | None:
34
+ """Return the group name for a sync asset, or None for default."""
35
+ return None
36
+
37
+ def get_description(self, sync_config: SyncConfig) -> str | None:
38
+ """Return the asset description."""
39
+ return sync_config.description
40
+
41
+ def get_deps(self, sync_config: SyncConfig) -> Sequence[AssetKey]:
42
+ """Return upstream asset dependencies for a sync."""
43
+ return []
44
+
45
+ def get_metadata(self, sync_config: SyncConfig) -> Mapping[str, Any]:
46
+ """Return static metadata to attach to the asset definition."""
47
+ return {}
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "dagster-drt"
7
+ version = "0.1.0"
8
+ description = "Community-maintained Dagster integration for drt (data reverse tool)"
9
+ readme = "README.md"
10
+ license = {text = "Apache-2.0"}
11
+ requires-python = ">=3.10"
12
+ dependencies = [
13
+ "dagster>=1.6",
14
+ "drt-core>=0.4.1",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ ]
File without changes
@@ -0,0 +1,160 @@
1
+ from pathlib import Path
2
+
3
+ from dagster import AssetKey
4
+
5
+ SYNC_YAML = "name: test_sync\nmodel: ref('users')\ndestination:\n type: rest_api\n url: http://example.com\n"
6
+ PROJECT_YAML = "name: test\nversion: '0.1'\nprofile: local\n"
7
+
8
+
9
+ def _setup_project(tmp_path: Path, syncs: dict[str, str] | None = None) -> Path:
10
+ """Create a minimal drt project structure for testing."""
11
+ (tmp_path / "drt_project.yml").write_text(PROJECT_YAML)
12
+ syncs_dir = tmp_path / "syncs"
13
+ syncs_dir.mkdir()
14
+ if syncs is None:
15
+ syncs = {"test_sync.yml": SYNC_YAML}
16
+ for filename, content in syncs.items():
17
+ (syncs_dir / filename).write_text(content)
18
+ return tmp_path
19
+
20
+
21
+ # --- Basic asset creation ---
22
+
23
+
24
+ def test_drt_assets_creates_asset_list(tmp_path: Path) -> None:
25
+ project = _setup_project(tmp_path)
26
+ from dagster_drt.assets import drt_assets
27
+
28
+ assets = drt_assets(project_dir=project)
29
+ assert len(assets) == 1
30
+
31
+
32
+ def test_drt_assets_filter_by_name(tmp_path: Path) -> None:
33
+ project = _setup_project(
34
+ tmp_path,
35
+ {
36
+ "a.yml": "name: a\nmodel: ref('t')\ndestination:\n type: rest_api\n url: http://x.com\n",
37
+ "b.yml": "name: b\nmodel: ref('t')\ndestination:\n type: rest_api\n url: http://x.com\n",
38
+ },
39
+ )
40
+ from dagster_drt.assets import drt_assets
41
+
42
+ assets = drt_assets(project_dir=project, sync_names=["a"])
43
+ assert len(assets) == 1
44
+
45
+
46
+ # --- Translator: default ---
47
+
48
+
49
+ def test_default_translator_asset_key(tmp_path: Path) -> None:
50
+ project = _setup_project(tmp_path)
51
+ from dagster_drt.assets import drt_assets
52
+
53
+ assets = drt_assets(project_dir=project)
54
+ assert assets[0].key == AssetKey("drt_test_sync")
55
+
56
+
57
+ def test_default_translator_no_group(tmp_path: Path) -> None:
58
+ project = _setup_project(tmp_path)
59
+ from dagster_drt.assets import drt_assets
60
+
61
+ assets = drt_assets(project_dir=project)
62
+ assert assets[0].group_names_by_key.get(AssetKey("drt_test_sync")) == "default"
63
+
64
+
65
+ # --- Translator: custom ---
66
+
67
+
68
+ def test_custom_translator_group_name(tmp_path: Path) -> None:
69
+ project = _setup_project(tmp_path)
70
+ from dagster_drt.assets import drt_assets
71
+ from dagster_drt.translator import DagsterDrtTranslator
72
+
73
+ class MyTranslator(DagsterDrtTranslator):
74
+ def get_group_name(self, sync_config):
75
+ return "reverse_etl"
76
+
77
+ assets = drt_assets(project_dir=project, dagster_drt_translator=MyTranslator())
78
+ assert assets[0].group_names_by_key[AssetKey("drt_test_sync")] == "reverse_etl"
79
+
80
+
81
+ def test_custom_translator_asset_key(tmp_path: Path) -> None:
82
+ project = _setup_project(tmp_path)
83
+ from dagster_drt.assets import drt_assets
84
+ from dagster_drt.translator import DagsterDrtTranslator
85
+
86
+ class MyTranslator(DagsterDrtTranslator):
87
+ def get_asset_key(self, sync_config):
88
+ return AssetKey(["my_prefix", sync_config.name])
89
+
90
+ assets = drt_assets(project_dir=project, dagster_drt_translator=MyTranslator())
91
+ assert assets[0].key == AssetKey(["my_prefix", "test_sync"])
92
+
93
+
94
+ def test_custom_translator_deps(tmp_path: Path) -> None:
95
+ project = _setup_project(tmp_path)
96
+ from dagster_drt.assets import drt_assets
97
+ from dagster_drt.translator import DagsterDrtTranslator
98
+
99
+ class MyTranslator(DagsterDrtTranslator):
100
+ def get_deps(self, sync_config):
101
+ return [AssetKey("upstream_model")]
102
+
103
+ assets = drt_assets(project_dir=project, dagster_drt_translator=MyTranslator())
104
+ dep_keys = {dep.asset_key for dep in assets[0].specs_by_key[AssetKey("drt_test_sync")].deps}
105
+ assert AssetKey("upstream_model") in dep_keys
106
+
107
+
108
+ # --- DrtConfig ---
109
+
110
+
111
+ def test_drt_assets_accepts_dry_run_default(tmp_path: Path) -> None:
112
+ """dry_run parameter should not break asset creation."""
113
+ project = _setup_project(tmp_path)
114
+ from dagster_drt.assets import drt_assets
115
+
116
+ assets = drt_assets(project_dir=project, dry_run=True)
117
+ assert len(assets) == 1
118
+
119
+
120
+ def test_drt_config_defaults() -> None:
121
+ from dagster_drt.assets import DrtConfig
122
+
123
+ config = DrtConfig()
124
+ assert config.dry_run is False
125
+
126
+
127
+ def test_drt_config_dry_run() -> None:
128
+ from dagster_drt.assets import DrtConfig
129
+
130
+ config = DrtConfig(dry_run=True)
131
+ assert config.dry_run is True
132
+
133
+
134
+ # --- Return type ---
135
+
136
+
137
+ def test_asset_returns_materialize_result(tmp_path: Path) -> None:
138
+ """Verify asset function return annotation is MaterializeResult."""
139
+ project = _setup_project(tmp_path)
140
+ from dagster import MaterializeResult
141
+
142
+ from dagster_drt.assets import drt_assets
143
+
144
+ assets = drt_assets(project_dir=project)
145
+ # Check the output type from the asset spec
146
+ spec = assets[0].specs_by_key[AssetKey("drt_test_sync")]
147
+ # MaterializeResult assets have no output type annotation on the spec
148
+ # but we can verify the asset was created successfully with the new signature
149
+ assert spec is not None
150
+
151
+
152
+ # --- Exports ---
153
+
154
+
155
+ def test_public_exports() -> None:
156
+ import dagster_drt
157
+
158
+ assert hasattr(dagster_drt, "drt_assets")
159
+ assert hasattr(dagster_drt, "DrtConfig")
160
+ assert hasattr(dagster_drt, "DagsterDrtTranslator")