dag-factory · v0.1.0 · 2026-08-23 · sha256 231c9b845c4bd857
dag-factory v0.1.0A
Immutable. This exact content is served forever at /api/v1/blob/231c9b845c4bd857.
---
name: dag-factory
description: "Author Airflow DAGs from dag-factory YAML. Use when creating or editing YAML DAG configs, loaders, callbacks, custom operators in YAML, dynamic mapping, datasets, or validating dag-factory files. Covers both map-style tasks (task_id as YAML key) used by in-repo plugins and the PyPI dag-factory v1 list format. Not for remote run/log inspection (vd:astro-airflow) or local astro dev lifecycle (vd:managing-astro-local-env)."
license: MIT
metadata:
author: vanducng
version: "0.1.0"
upstream: "https://github.com/astronomer/agents/tree/main/skills/dag-factory"
---
# dag-factory
Declarative Airflow DAGs from YAML. Detect the repo's dialect **before** writing YAML. Official Astronomer skill targets PyPI `dag-factory` v1+ (list-format `tasks`). Many production repos still use a vendored plugin and **map-format** `tasks` keyed by `task_id`. Mixing the two breaks parse.
## 1. Detect dialect
```bash
rg -n "load_yaml_dags|from dagfactory|from plugins.dagfactory" --glob '*.py' dags plugins | head
rg -n "^ tasks:" -g '*.yaml' -g '*.yml' -A 6 | head -40
```
| Signal | Dialect |
|---|---|
| `from plugins.dagfactory import load_yaml_dags` (or similar in-repo plugin) | **map** - `tasks.<task_id>.operator` |
| `from dagfactory import load_yaml_dags` + `dag-factory>=1` in deps | **list** - `tasks: [{task_id, operator}]` |
| `tasks:` then a nested key that is a task id, not `- task_id:` | **map** |
| `tasks:` then `- task_id:` | **list** |
Match neighboring YAML in the same folder. Do not "modernize" map-format files to list-format unless the user asks and the loader is PyPI v1+.
Keep `from airflow import DAG` in every loader module even if it looks unused. The DAG processor requires it.
## 2. Loader
Map-style in-repo plugin (typical):
```python
from airflow import DAG
from plugins.dagfactory import load_yaml_dags
load_yaml_dags(globals_dict=globals(), dags_folder=".../configs")
```
PyPI v1:
```python
from airflow import DAG
from dagfactory import load_yaml_dags
load_yaml_dags(globals_dict=globals(), dags_folder="/usr/local/airflow/dags")
```
`globals_dict=globals()` is required.
## 3. YAML shape
### Map format (in-repo / pre-1.0)
```yaml
default:
default_args:
owner: data-team
retries: 3
retry_delay_sec: 180
on_failure_callback: package.callbacks.task_failed
on_failure_callback: package.callbacks.dag_failed
catchup: false
elt_source_object:
schedule: 0 2 * * *
max_active_runs: 1
params:
stg_database: AIRFLOW_VARIABLE__source/stg_database
tasks:
extract:
operator: package.operators.extract.ExtractOperator
http_conn: source_conn
load:
operator: package.operators.load.LoadOperator
dependencies:
- extract
```
Task id = YAML key. `dependencies` is a list of upstream task ids (not `>>`).
### List format (PyPI v1+)
```yaml
default:
default_args:
start_date: 2025-01-01
elt_source_object:
schedule: 0 2 * * *
catchup: false
tasks:
- task_id: extract
operator: airflow.providers.standard.operators.empty.EmptyOperator
- task_id: load
operator: airflow.providers.standard.operators.empty.EmptyOperator
dependencies: [extract]
```
## 4. Hard rules
- **Full operator import path.** `airflow.providers.standard.operators.python.PythonOperator`, not a short name. Airflow 3: prefer `airflow.providers.*` over `airflow.operators.*`.
- **Callbacks are string paths** that the factory imports. Two layers: task `default_args.on_failure_callback` (after retries) plus DAG `on_failure_callback` (run summary). DAG-level does **not** fire on each failed task.
- **Do not add `on_retry_callback`** unless the user asks. Retries are expected.
- **Params** may use an `AIRFLOW_VARIABLE__` prefix when the factory resolves Airflow Variables at parse/runtime. Copy the prefix from sibling YAML; do not invent it.
- **Custom operators** live in Python (`plugins/operators/` or equivalent). YAML only references them. New reusable logic → operator + YAML task, not a one-off Python DAG.
- **Python callables** in YAML: `python_callable_file` + `python_callable_name` (absolute path inside the image, usually `/usr/local/airflow/dags/...`).
- **`max_active_runs: 1`** means a manual trigger can sit queued behind a scheduled run.
## 5. Validate
Prefer parse over hoping the UI will show the DAG:
```bash
astro dev parse
```
If the repo wraps Airflow CLI:
```bash
# only if these targets exist
make dags-import-errors
make airflow CMD='dags list'
```
Broken YAML = import error, not a runtime task failure. Fix the YAML/loader, then re-parse.
PyPI v1 also ships `dagfactory convert` (Airflow 2 YAML → 3 operator paths). Do not run convert on map-format in-repo YAML.
## 6. New DAG checklist
1. Read one sibling YAML in the same source folder and copy structure.
2. DAG id: `[job_type]_[source]_[object]` if the repo already uses that pattern (`elt_`, `mon_`, `mnt_`, `rpt_`, ...).
3. Loader `dag.py` in the source folder; YAML under `configs/`.
4. Both callback layers + `catchup: false` unless backfill is the point.
5. `astro dev parse` clean before claiming done.
## 7. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| DAG missing in UI | YAML syntax, bad operator path, missing `from airflow import DAG` | `astro dev parse` / import-errors |
| `cannot import name 'SUPERVISOR_COMMS'` | `Variable.get()` at parse time on Airflow 3 | Do not call `Variable.get` in loader; use `AIRFLOW_VARIABLE__` params or try/except fallback |
| Task never runs | wrong `dependencies` key vs list-format `task_id` | Match file dialect |
| Callback import error | dotted path not importable in the image | Use the same prefix as sibling YAML |
| Official v1 examples fail to parse | list-format YAML in a map-format factory | Rewrite as map keys |
## Related
- Remote runs/logs: `vd:astro-airflow`
- Local `astro dev`: `vd:managing-astro-local-env`
- Otto: `vd:delegating-to-otto`
- Upstream v1 reference: https://github.com/astronomer/dag-factory
- Official skill (list-format): https://github.com/astronomer/agents/blob/main/skills/dag-factory/SKILL.md