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.
- dagster_drt-0.1.0/.gitignore +44 -0
- dagster_drt-0.1.0/PKG-INFO +139 -0
- dagster_drt-0.1.0/README.md +123 -0
- dagster_drt-0.1.0/dagster_drt/__init__.py +4 -0
- dagster_drt-0.1.0/dagster_drt/assets.py +134 -0
- dagster_drt-0.1.0/dagster_drt/translator.py +47 -0
- dagster_drt-0.1.0/pyproject.toml +23 -0
- dagster_drt-0.1.0/tests/__init__.py +0 -0
- dagster_drt-0.1.0/tests/test_assets.py +160 -0
|
@@ -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
|
+
[](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
|
+
[](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,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")
|