Niklas Heringer
Niklas Heringer
Cybersecurity & Math.
⌘K
  • Home
  • Blog
  • #My Work
  • My publications
  • GitHub
  • My Linkedin
  • Events and Traveling
  • Armory
  • Next
  • My Medium
  • #Aspects
  • Penetration Testing
  • Skills Lab
  • Research
  • Digital Forensics
  • Security News
  • Field Notes
  • #Info and Guidance
  • Privacy Policy and Cookie Policy
  • Impressum
Subscribe Sign in
Niklas Heringer
members-only post Apr 05, 2025 dagster

Data Engineering With Dagster Part Eight: Metadata

Dig into materialization metadata, inline visualizations, and best practices for asset observability using Dagster.
Data Engineering With Dagster Part Eight: Metadata
$ stat data-engineering-with-dagster-part-eight.md
File:
data-engineering-with-dagster-part-eight.md
Modified:
2025-11-18 21:53
Read:
4 min read
Tags:
dagster

On this page

Metadata in Dagster: The Two Worlds

Metadata is data about data - and while that sounds meta (and it is), it’s also the secret to making your pipelines understandable, traceable, and collaborative.

Imagine you're running a bakery:

  • The cookies = your data
  • The label saying “baked today at 7:32am, by Sam” = your metadata

In Dagster, metadata powers both visibility and observability. There are two types you’ll work with:

  • Definition metadata → fixed context like descriptions and groupings
  • Materialization metadata → dynamic info like row counts, timestamps, or even rendered charts from each run

We’ll explore both, starting with how to better describe your data for your team - and future you.


Definition Metadata: Describe Your Assets

Dagster gives you two clean ways to document your assets:

Option 1: Python Docstrings

Add a triple-quoted string at the top of your asset function - Dagster will pick it up:

@dg.asset
def taxi_zones_file() -> None:
    """
    The raw CSV file for the taxi zones dataset. Sourced from the NYC Open Data portal.
    """

These show up in the Dagster UI - perfect for quick inline docs.

Option 2: The description= Parameter

You can also add a description directly in the decorator:

@dg.asset(
    description="The raw CSV file for the taxi zones dataset. Sourced from the NYC Open Data portal."
)
def taxi_zones_file() -> None:
    """This docstring won’t be shown in the UI."""

If both exist, Dagster uses the description=. Handy if you want internal vs. external descriptions.

Where You See It

  • In the Assets tab under the asset name
  • In the Global Asset Lineage graph, when you hover over a node

Documentation becomes part of your code - and your UI.


Grouping Assets: Don’t Let It Get Messy

As your project grows, your assets multiply. It’s time to group them - not just visually, but functionally.

You can group assets:

  • Individually using the group_name= parameter
  • By module, using load_assets_from_modules(..., group_name=...)

Both result in clearly labeled boxes in the Dagster UI - and more maintainable job selection.

Grouping Individual Assets

@dg.asset(group_name="raw_files")
def taxi_zones_file() -> None:
    ...
@dg.asset(group_name="ingested")
def taxi_trips() -> None:
    ...

Grouping Entire Modules

In definitions.py:

metric_assets = dg.load_assets_from_modules(
    modules=[metrics],
    group_name="metrics"
)

This is clean, scalable, and ideal for large projects.


Mini Excursus: Practice Grouping

Try it like this:

# assets/trips.py
@dg.asset(group_name="raw_files")
def taxi_trips_file() -> None:
    ...

@dg.asset(group_name="ingested")
def taxi_trips() -> None:
    ...
# definitions.py
request_assets = dg.load_assets_from_modules(
    modules=[requests],
    group_name="requests"
)

Now, in the UI, assets are visually separated into raw_files, ingested, and requests.

No more asset soup. Just order.


Materialization Metadata: Add Context to Each Run

While definition metadata is about what the asset is, materialization metadata is about what happened during a run.

This might include:

  • Number of records processed
  • Size of output files
  • Execution timestamps
  • Even preview charts or markdown

Let’s implement a common one: row count.


Adding Row Count to taxi_trips_file

In assets/trips.py:

import pandas as pd
import dagster as dg

@dg.asset(
    partitions_def=monthly_partition,
    group_name="raw_files",
)
def taxi_trips_file(context) -> dg.MaterializeResult:
    """
    The raw parquet files for the taxi trips dataset. Sourced from the NYC Open Data portal.
    """
    partition_date_str = context.partition_key
    month_to_fetch = partition_date_str[:-3]

    raw_trips = requests.get(
        f"https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_{month_to_fetch}.parquet"
    )

    file_path = constants.TAXI_TRIPS_TEMPLATE_FILE_PATH.format(month_to_fetch)

    with open(file_path, "wb") as output_file:
        output_file.write(raw_trips.content)

    num_rows = len(pd.read_parquet(file_path))

    return dg.MaterializeResult(
        metadata={
            "Number of records": dg.MetadataValue.int(num_rows)
        }
    )

Mini Excursus: Do It for taxi_zones_file

@dg.asset(group_name="raw_files")
def taxi_zones_file() -> dg.MaterializeResult:
    raw_taxi_zones = requests.get(
        "https://community-engineering-artifacts.s3.us-west-2.amazonaws.com/dagster-university/data/taxi_zones.csv"
    )

    with open(constants.TAXI_ZONES_FILE_PATH, "wb") as output_file:
        output_file.write(raw_taxi_zones.content)

    num_rows = len(pd.read_csv(constants.TAXI_ZONES_FILE_PATH))

    return dg.MaterializeResult(
        metadata={
            "Number of records": dg.MetadataValue.int(num_rows)
        }
    )

Viewing in the UI

Once materialized:

  • Open Global Asset Lineage
  • Select taxi_trips_file
  • View row count per partition as a graph
Plots in UI

Real-time dashboards. Zero setup.


Markdown Metadata: Inline Chart Previews

Some metadata is visual.

Let’s embed a generated chart (e.g. from adhoc_request) as a rendered Markdown image in the UI.

Here’s how:

import base64

with open(file_path, "rb") as file:
    image_data = file.read()

base64_data = base64.b64encode(image_data).decode('utf-8')
md_content = f"![Image](data:image/jpeg;base64,{base64_data})"

return dg.MaterializeResult(
    metadata={
        "preview": dg.MetadataValue.md(md_content)
    }
)

Dagster renders it directly in the UI:

Markdown preview

It’s like adding screenshots to your pipeline logs - but built in.


Knowledge Check

This post is for subscribers only

Subscribe to continue reading


More like this

System One AI for Cybersecurity: Not Every Decision Needs a Generative Model

System One AI for Cybersecurity: Not Every Decision Needs a Generative Model

Security pipelines ask frontier LLMs millions of (basically) yes/no questions. I tested whether cheap decision models like Jev can triage HTTP responses instead: four models, three LLMs, one open harness. The answer is a qualified yes, plus a warning about what the gate throws away.
11 Oct
Pentesting Passkeys: A Practical Methodology for WebAuthn Deployments

Pentesting Passkeys: A Practical Methodology for WebAuthn Deployments

Recon, server-side Relying Party tests, and the Windows-focused OS and authenticator attacks: end-to-end, tool by tool.
15 Aug
Field Notes from Black Hat US 2026 and DEF CON 34

Field Notes from Black Hat US 2026 and DEF CON 34

Field notes from Black Hat USA 2026 and DEF CON 34: the talks that mattered to me most, honest questions about where AI-accelerated research is heading, and the deep dives I'm committing to next.
11 Aug
A Hacker's Guide to Post-Quantum Cryptography: ML-KEM & FIPS 203

A Hacker's Guide to Post-Quantum Cryptography: ML-KEM & FIPS 203

Everything you need to understand the algorithm that's replacing RSA (in terms of Key Exchange), from polynomial rings to key encapsulation, with zero hand-waving.
17 May
Linux Lab Pitfalls That Will Cost You Hours

Linux Lab Pitfalls That Will Cost You Hours

Netcat, MTU, SUID, shell stabilisation; Linux lab concepts most resources skip. Explanations, fixes, quizzes to test your depth.
11 May
Windows Privilege Escalation 02: Running through PrivEsc Paths from Zero

Windows Privilege Escalation 02: Running through PrivEsc Paths from Zero

Working through HackTheBox's Windows Privilege Escalation module as a beginner. Every token abuse, group membership exploit, and real mistake documented. SeImpersonate, DnsAdmins, Server Operators, SeBackupPrivilege and more. No filler, just what got me SYSTEM.
03 May
Niklas Heringer © 2026. Published with Ghost & Braun
  • Sign up
Privacy settings niklas-heringer.com

This website uses strictly necessary cookies and browser storage (member sign-in, security, your light or dark choice and this notice). With your consent it also measures anonymous visitor statistics. No advertising or cross-site tracking. Details in our Privacy Policy.

Privacy information
Strictly necessary Always on

Cookies and browser storage needed for the site to work: member sign-in (only after you log in), security features, your light or dark mode choice and the record of this notice. If paid memberships are offered, Stripe sets fraud-prevention cookies during checkout only.

Visitor statistics

Anonymous page-view statistics (Ghost Traffic Analytics, processed by Tinybird in the EU): visited page, referrer, browser and country. No cookies, not used for advertising or tracking across sites. Without your consent nothing is sent.

Newsletter emails Members only

If you subscribe, emails are sent via Mailgun (EU region). Delivery and open events are recorded for the newsletter. You can unsubscribe at any time with the link in every email or in your account.