Skip to content

Commit 30e4b06

Browse files
authored
Testing with Dagster dg (#97)
1 parent 6a80a0b commit 30e4b06

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1296
-948
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
FROM mcr.microsoft.com/devcontainers/python:0-3.11-bullseye
22
ENV PYTHONUNBUFFERED 1
33

4-
COPY --from=ghcr.io/astral-sh/uv:0.4.7 /uv /bin/uv
4+
COPY --from=ghcr.io/astral-sh/uv:0.6.10 /uv /bin/uv
55

66
COPY dagster_university/dagster_testing/pyproject.toml .
77
RUN uv pip install -r pyproject.toml --system

course/pages/dagster-testing/lesson-2/1-set-up-local.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ lesson: '2'
66

77
# Set up local
88

9+
This will set up Dagster for you local machine. If you would prefer to do this course in Github Codespaces, please follow [that guide](/dagster-testing/lesson-2/2-set-up-codespace).
10+
911
- **To install git.** Refer to the [Git documentation](https://github.com/git-guides/install-git) if you don’t have this installed.
1012
- **To have Python installed.** Dagster supports Python 3.9 - 3.12.
1113
- **To install a package manager**. To manage the python packages, we recommend [`uv`]((https://docs.astral.sh/uv/)) which Dagster uses internally.
@@ -34,38 +36,37 @@ After cloning the Dagster University project, you’ll want to navigate to speci
3436
cd dagster_university/dagster_testing
3537
```
3638

37-
## Install the dependencies
39+
## Install uv and dg
3840

39-
**uv**
41+
Now we want to install `dg`. This is the command line interface that makes it easy to interact with Dagster. Throughout the course we will use `dg` to scaffold our project and streamline the development process.
4042

41-
To install the python dependencies with [uv](https://docs.astral.sh/uv/).
43+
In order to best use `dg` we will need the Python package manager [`uv`](https://docs.astral.sh/uv/). `uv` will allow us to install `dg` globally and more easily build our virtual environments.
4244

45+
If you do not have `uv` instead already, you can do so with:
4346
```bash
44-
uv sync
47+
brew install uv
4548
```
4649

47-
This will create a virtual environment that you can now use.
48-
50+
Now you can use `uv` to install `dg` globally:
4951
```bash
50-
source .venv/bin/activate
52+
uv tool install dagster-dg
5153
```
5254

53-
**pip**
54-
55-
Create the virtual environment.
55+
## Install the dependencies
5656

57+
With `uv` and `dg` set, we can create the virtual environment specific to this course. All of the dependencies are maintained in the `pyproject.toml` (you will not need to edit anything in that project for this course). To create the virtual environment, run:
5758
```bash
58-
python3 -m venv .venv
59+
uv sync
5960
```
6061

61-
Enter the virtual environment.
62+
This will create a virtual environment and install all the necessary dependencies. To activate this virtual environment:
6263

6364
```bash
6465
source .venv/bin/activate
6566
```
6667

67-
Install the packages.
68+
To ensure everything is working you can launch the Dagster UI.
6869

6970
```bash
70-
pip install -e ".[dev]"
71+
dg dev
7172
```

course/pages/dagster-testing/lesson-2/2-set-up-codespace.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ cd dagster_university/dagster_testing
4141
To ensure everything is working you can launch the Dagster UI.
4242

4343
```bash
44-
dagster dev
44+
dg dev
4545
```
4646

4747
After Dagster starts running you will be prompted to open the Dagster UI within your browser. Click "Open in Browser".

course/pages/dagster-testing/lesson-3/1-unit-tests.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Dagster assets are good candidates for unit tests. Since an asset is responsible
3434
We will begin with the following asset:
3535

3636
```python
37-
# /dagster_testing/assets/lesson_3.py
37+
# /dagster_testing/defs/assets/lesson_3.py
3838
@dg.asset
3939
def state_population_file() -> list[dict]:
4040
file_path = Path(__file__).absolute().parent / "../data/ny.csv"

course/pages/dagster-testing/lesson-3/2-testing-dependencies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Most Dagster asset graphs contain multiple assets that depend on the output of o
1111
We will add an additional asset downstream of `state_population_file` that takes in its output:
1212

1313
```python
14-
# /dagster_testing/assets/lesson_3.py
14+
# /dagster_testing/defs/assets/lesson_3.py
1515
@dg.asset
1616
def total_population(state_population_file: list[dict]) -> int:
1717
return sum([int(x["Population"]) for x in state_population_file])

course/pages/dagster-testing/lesson-3/3-testing-assets-with-run-configurations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Some assets in Dagster pipelines may take in parameters defined outside of asset
1111
If we think about the `state_population_file` it can currently only parse a single file. Let's create a new asset called `state_population_file_config` with a run configuration. This asset will be able to process any file:
1212

1313
```python
14-
# /dagster_testing/assets/lesson_3.py
14+
# /dagster_testing/defs/assets/lesson_3.py
1515
class FilepathConfig(dg.Config):
1616
path: str
1717

course/pages/dagster-testing/lesson-3/4-testing-asset-output-types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ lesson: '3'
99
In standard Python, a function does not need to match its type annotation in order to execute properly. For example:
1010

1111
```python
12-
# /dagster_testing/assets/lesson_3.py
12+
# /dagster_testing/defs/assets/lesson_3.py
1313
def func_wrong_type() -> str:
1414
return 2
1515
```

course/pages/dagster-testing/lesson-3/5-testing-assets-with-execution-context.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ If your assets do not access any of the context APIs, you will not need to worry
1313
However if we rewrite the `state_population_file` asset to include context logging, we will need to update our tests:
1414

1515
```python
16-
# /dagster_testing/assets/lesson_3.py
16+
# /dagster_testing/defs/assets/lesson_3.py
1717
@dg.asset()
1818
def state_population_file_logging(context: dg.AssetExecutionContext) -> list[dict]:
1919
file_path = Path(__file__).absolute().parent / "../data/ny.csv"

course/pages/dagster-testing/lesson-4/1-testing-with-mocks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Imagine that an API exists to query city populations by state (sadly this API do
3030
We want to rewrite the `state_population_file` asset to use this endpoint instead of reading a file to retrieve the necessary data. This is what the new asset will look like.
3131

3232
```python
33-
# /dagster_testing/assets/lesson_4.py
33+
# /dagster_testing/defs/assets/lesson_4.py
3434
API_URL = "https://fake.com/population.json"
3535

3636

course/pages/dagster-testing/lesson-4/2-testing-with-resources.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Resources are objects that provide access to external systems, databases, or ser
1313
We can refactor the API asset code into a resource. A resource is just a class that inherits from `dg.ConfigurableResource`. It can have any number of methods which assets can use. This resource will only include a single method for `get_cities`.
1414

1515
```python
16-
# /dagster_testing/assets/lesson_4.py
16+
# /dagster_testing/defs/assets/lesson_4.py
1717
class StatePopulation(dg.ConfigurableResource):
1818
def get_cities(self, state: str) -> list[dict]:
1919
output = []

course/pages/dagster-testing/lesson-4/3-materializing-resource-tests.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ lesson: '4'
99
When we discussed unit tests we showed how you can execute one or more assets together using `dg.materialize()`. We can still materialize our assets this way using mocks.
1010

1111
```python
12-
# /dagster_testing/assets/lesson_4.py
12+
# /dagster_testing/defs/assets/lesson_4.py
1313
@patch("requests.get")
1414
def test_state_population_api_assets(mock_get, example_response, api_output):
1515
mock_response = Mock()

course/pages/dagster-testing/lesson-5/2-integration-tests.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Assume a table exists in our production data warehouse, `data.city_population`,
1919
We will hardcode our asset to look for the cities in New York and return the results from Snowflake using a resource.
2020

2121
```python
22-
# /dagster_testing/assets/lesson_5.py
22+
# /dagster_testing/defs/assets/lesson_5.py
2323
@dg.asset
2424
def state_population_database(database: SnowflakeResource) -> list[tuple]:
2525
query = """

course/pages/dagster-testing/lesson-6/1-asset-checks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ When the asset runs, we can see that its associated asset checks also run and va
2323
To define an asset check we first need an asset. `total_population` is a slightly modified version of the asset we have used throughout the course. Now it will take in the output of several assets and sums their populations.
2424

2525
```python
26-
# /dagster_testing/assets/lesson_6.py
26+
# /dagster_testing/defs/assets/lesson_6.py
2727
@dg.asset
2828
def total_population(
2929
state_population_file_config: list[dict],

course/pages/dagster-testing/lesson-6/2-definitions.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ lesson: '6'
66

77
# Definitions
88

9-
Within your Dagster project the most important object is the definition. This defines all the objects that will deployed into your code location. Because of its importance we will want to write a test for it.
9+
Within your Dagster project the most important object is the `Definitions`. This defines all the objects that will deployed into your code location. If you are using `dg` you may already be in the habit of checking to ensure your `Definitions` is valid by running `dg check defs`.
10+
11+
This is a great habit and you can build out workflows (such as precommit hooks) to always run that check. But it is also good to get in the habit of writing a specific test for this to live alongside your other Dagster tests.
1012

1113
Luckily this is a very easy test to write.
1214

course/pages/dagster-testing/lesson-6/3-dagster-objects.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ In order to write a reliable test for this sensor, we will go back to our lesson
128128
First we can set a test where our sensor will skip.
129129

130130
```python
131-
@patch("dagster_testing.sensors.check_for_new_files", return_value=[])
131+
@patch("dagster_testing.defs.sensors.check_for_new_files", return_value=[])
132132
def test_sensor_skip(mock_check_new_files):
133133
instance = dg.DagsterInstance.ephemeral()
134134
context = dg.build_sensor_context(instance=instance)
@@ -152,7 +152,7 @@ What would it look like to write a test to ensure the sensor picks up a new file
152152

153153
```python {% obfuscated="true" %}
154154
@patch(
155-
"dagster_testing.sensors.check_for_new_files",
155+
"dagster_testing.defs.sensors.check_for_new_files",
156156
return_value=["test_file"],
157157
)
158158
def test_sensor_run(mock_check_new_files):
Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,5 @@
11
import dagster as dg
22

3-
import dagster_testing.jobs as jobs
4-
import dagster_testing.resources as resources
5-
import dagster_testing.schedules as schedules
6-
import dagster_testing.sensors as sensors
7-
from dagster_testing.assets import lesson_3, lesson_4, lesson_5, lesson_6
3+
import dagster_testing.defs
84

9-
lesson_3_assets = dg.load_assets_from_modules([lesson_3])
10-
lesson_4_assets = dg.load_assets_from_modules([lesson_4])
11-
lesson_5_assets = dg.load_assets_from_modules([lesson_5])
12-
lesson_6_assets = dg.load_assets_from_modules([lesson_6])
13-
14-
15-
defs = dg.Definitions(
16-
assets=lesson_3_assets + lesson_4_assets + lesson_5_assets + lesson_6_assets,
17-
asset_checks=[lesson_6.non_negative],
18-
jobs=[jobs.my_job, jobs.my_job_configured],
19-
resources={
20-
"state_population_resource": resources.StatePopulation(),
21-
"database": dg.ResourceDefinition.mock_resource(),
22-
},
23-
schedules=[schedules.my_schedule],
24-
sensors=[sensors.my_sensor],
25-
)
5+
defs = dg.components.load_defs(dagster_testing.defs)

dagster_university/dagster_testing/dagster_testing/assets/lesson_6.py renamed to dagster_university/dagster_testing/dagster_testing/defs/assets/lesson_6.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import dagster as dg
55

6-
import dagster_testing.resources as resources
6+
import dagster_testing.defs.resources as resources
77

88

99
class FilepathConfig(dg.Config):

dagster_university/dagster_testing/dagster_testing/jobs.py renamed to dagster_university/dagster_testing/dagster_testing/defs/jobs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import dagster as dg
44
import yaml
55

6-
from dagster_testing.assets import lesson_6
6+
from dagster_testing.defs.assets import lesson_6
77

88
my_job = dg.define_asset_job(
99
name="jobs",

dagster_university/dagster_testing/dagster_testing/resources.py renamed to dagster_university/dagster_testing/dagster_testing/defs/resources.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,11 @@ def get_cities(self, state: str) -> list[dict]:
1313
"Population": 269840,
1414
},
1515
]
16+
17+
18+
defs = dg.Definitions(
19+
resources={
20+
"state_population_resource": StatePopulation(),
21+
"database": dg.ResourceDefinition.mock_resource(),
22+
},
23+
)

dagster_university/dagster_testing/dagster_testing/schedules.py renamed to dagster_university/dagster_testing/dagster_testing/defs/schedules.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import dagster as dg
22

3-
import dagster_testing.jobs as jobs
4-
from dagster_testing.assets import lesson_6
3+
import dagster_testing.defs.jobs as jobs
4+
from dagster_testing.defs.assets import lesson_6
55

66
my_schedule = dg.ScheduleDefinition(
77
name="my_schedule",

dagster_university/dagster_testing/dagster_testing/sensors.py renamed to dagster_university/dagster_testing/dagster_testing/defs/sensors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import dagster as dg
44

5-
import dagster_testing.jobs as jobs
5+
import dagster_testing.defs.jobs as jobs
66

77

88
def check_for_new_files() -> list[str]:

dagster_university/dagster_testing/dagster_testing_tests/completed/test_lesson_3.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import yaml
66
from dagster._core.errors import DagsterTypeCheckDidNotPass
77

8-
from dagster_testing.assets import lesson_3
8+
from dagster_testing.defs.assets import lesson_3
99

1010

1111
@pytest.fixture()

dagster_university/dagster_testing/dagster_testing_tests/completed/test_lesson_4.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import dagster as dg
44
import pytest
55

6-
from dagster_testing.assets import lesson_4
6+
from dagster_testing.defs.assets import lesson_4
77

88

99
@pytest.fixture

dagster_university/dagster_testing/dagster_testing_tests/completed/test_lesson_5.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66
from dagster_snowflake import SnowflakeResource
77

8-
from dagster_testing.assets import lesson_5
8+
from dagster_testing.defs.assets import lesson_5
99

1010
from ..fixtures import docker_compose # noqa: F401
1111

dagster_university/dagster_testing/dagster_testing_tests/completed/test_lesson_6.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
import dagster as dg
55
import pytest
66

7-
import dagster_testing.jobs as jobs
8-
import dagster_testing.resources as resources
9-
import dagster_testing.schedules as schedules
10-
import dagster_testing.sensors as sensors
11-
from dagster_testing.assets import lesson_6
7+
import dagster_testing.defs.jobs as jobs
8+
import dagster_testing.defs.resources as resources
9+
import dagster_testing.defs.schedules as schedules
10+
import dagster_testing.defs.sensors as sensors
1211
from dagster_testing.definitions import defs
12+
from dagster_testing.defs.assets import lesson_6
1313

1414

1515
@pytest.fixture()
@@ -128,15 +128,15 @@ def test_sensors():
128128
assert sensors.my_sensor
129129

130130

131-
@patch("dagster_testing.sensors.check_for_new_files", return_value=[])
131+
@patch("dagster_testing.defs.sensors.check_for_new_files", return_value=[])
132132
def test_sensor_skip(mock_check_new_files):
133133
instance = dg.DagsterInstance.ephemeral()
134134
context = dg.build_sensor_context(instance=instance)
135135
assert sensors.my_sensor(context).__next__() == dg.SkipReason("No new files found")
136136

137137

138138
@patch(
139-
"dagster_testing.sensors.check_for_new_files",
139+
"dagster_testing.defs.sensors.check_for_new_files",
140140
return_value=["test_file"],
141141
)
142142
def test_sensor_run(mock_check_new_files):

dagster_university/dagster_testing/dagster_testing_tests/test_lesson_3.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import yaml # noqa: F401
55
from dagster._core.errors import DagsterTypeCheckDidNotPass # noqa: F401
66

7-
from dagster_testing.assets import lesson_3
7+
from dagster_testing.defs.assets import lesson_3
88

99

1010
@pytest.fixture()

dagster_university/dagster_testing/dagster_testing_tests/test_lesson_4.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import dagster as dg # noqa: F401
44
import pytest
55

6-
from dagster_testing.assets import lesson_4 # noqa: F401
6+
from dagster_testing.defs.assets import lesson_4 # noqa: F401
77

88

99
@pytest.fixture

dagster_university/dagster_testing/dagster_testing_tests/test_lesson_5.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66
from dagster_snowflake import SnowflakeResource
77

8-
from dagster_testing.assets import lesson_5
8+
from dagster_testing.defs.assets import lesson_5
99

1010
from .fixtures import docker_compose # noqa: F401
1111

dagster_university/dagster_testing/dagster_testing_tests/test_lesson_6.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import pytest
22

3-
import dagster_testing.jobs as jobs # noqa: F401
4-
import dagster_testing.schedules as schedules # noqa: F401
5-
import dagster_testing.sensors as sensors # noqa: F401
6-
from dagster_testing.assets import lesson_6 # noqa: F401
3+
import dagster_testing.defs.jobs as jobs # noqa: F401
4+
import dagster_testing.defs.schedules as schedules # noqa: F401
5+
import dagster_testing.defs.sensors as sensors # noqa: F401
76
from dagster_testing.definitions import defs
7+
from dagster_testing.defs.assets import lesson_6 # noqa: F401
88

99

1010
@pytest.fixture()

0 commit comments

Comments
 (0)